diff mbox

[2/3] drm:msm: Initial Add Writeback Support (V2)

Message ID 1428430164-31874-1-git-send-email-jilaiw@codeaurora.org (mailing list archive)
State New, archived
Headers show

Commit Message

jilai wang April 7, 2015, 6:09 p.m. UTC
Add writeback support in msm kms framework.
V1: Initial change
V2: Address Rob/Paul/Emil's comments

Signed-off-by: Jilai Wang <jilaiw@codeaurora.org>
---
 drivers/gpu/drm/msm/Kconfig                       |  10 +
 drivers/gpu/drm/msm/Makefile                      |   7 +
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c           |  10 +
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h           |   1 +
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c           |  17 +-
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h           |   8 +
 drivers/gpu/drm/msm/mdp/mdp5/mdp5_wb_encoder.c    | 466 ++++++++++++++++++++
 drivers/gpu/drm/msm/mdp/mdp_kms.h                 |   2 +-
 drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.c           | 311 ++++++++++++++
 drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.h           |  98 +++++
 drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_connector.c | 157 +++++++
 drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_v4l2.c      | 501 ++++++++++++++++++++++
 drivers/gpu/drm/msm/msm_drv.c                     |   2 +
 drivers/gpu/drm/msm/msm_drv.h                     |  15 +
 drivers/gpu/drm/msm/msm_fbdev.c                   |  34 +-
 15 files changed, 1636 insertions(+), 3 deletions(-)
 create mode 100644 drivers/gpu/drm/msm/mdp/mdp5/mdp5_wb_encoder.c
 create mode 100644 drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.c
 create mode 100644 drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.h
 create mode 100644 drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_connector.c
 create mode 100644 drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_v4l2.c

Comments

Rob Clark Aug. 19, 2015, 7 p.m. UTC | #1
(()()

On Tue, Apr 7, 2015 at 2:09 PM, Jilai Wang <jilaiw@codeaurora.org> wrote:
> Add writeback support in msm kms framework.
> V1: Initial change
> V2: Address Rob/Paul/Emil's comments
>
> Signed-off-by: Jilai Wang <jilaiw@codeaurora.org>
> ---
>  drivers/gpu/drm/msm/Kconfig                       |  10 +
>  drivers/gpu/drm/msm/Makefile                      |   7 +
>  drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c           |  10 +
>  drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h           |   1 +
>  drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c           |  17 +-
>  drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h           |   8 +
>  drivers/gpu/drm/msm/mdp/mdp5/mdp5_wb_encoder.c    | 466 ++++++++++++++++++++
>  drivers/gpu/drm/msm/mdp/mdp_kms.h                 |   2 +-
>  drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.c           | 311 ++++++++++++++
>  drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.h           |  98 +++++
>  drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_connector.c | 157 +++++++
>  drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_v4l2.c      | 501 ++++++++++++++++++++++
>  drivers/gpu/drm/msm/msm_drv.c                     |   2 +
>  drivers/gpu/drm/msm/msm_drv.h                     |  15 +
>  drivers/gpu/drm/msm/msm_fbdev.c                   |  34 +-
>  15 files changed, 1636 insertions(+), 3 deletions(-)
>  create mode 100644 drivers/gpu/drm/msm/mdp/mdp5/mdp5_wb_encoder.c
>  create mode 100644 drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.c
>  create mode 100644 drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.h
>  create mode 100644 drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_connector.c
>  create mode 100644 drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_v4l2.c
>
> diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig
> index 0a6f676..5754d12 100644
> --- a/drivers/gpu/drm/msm/Kconfig
> +++ b/drivers/gpu/drm/msm/Kconfig
> @@ -46,3 +46,13 @@ config DRM_MSM_DSI
>           Choose this option if you have a need for MIPI DSI connector
>           support.
>
> +config DRM_MSM_WB
> +       bool "Enable writeback support for MSM modesetting driver"
> +       depends on DRM_MSM
> +       depends on VIDEO_V4L2
> +       select VIDEOBUF2_CORE
> +       default y
> +       help
> +         Choose this option if you have a need to support writeback
> +         connector.
> +
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index ab20867..fd2b0bb 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -1,4 +1,5 @@
>  ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/msm
> +ccflags-$(CONFIG_DRM_MSM_WB) += -Idrivers/gpu/drm/msm/mdp/mdp_wb
>
>  msm-y := \
>         adreno/adreno_device.o \
> @@ -56,4 +57,10 @@ msm-$(CONFIG_DRM_MSM_DSI) += dsi/dsi.o \
>                         dsi/dsi_phy.o \
>                         mdp/mdp5/mdp5_cmd_encoder.o
>
> +msm-$(CONFIG_DRM_MSM_WB) += \
> +       mdp/mdp5/mdp5_wb_encoder.o \
> +       mdp/mdp_wb/mdp_wb.o \
> +       mdp/mdp_wb/mdp_wb_connector.o \
> +       mdp/mdp_wb/mdp_wb_v4l2.o
> +
>  obj-$(CONFIG_DRM_MSM)  += msm.o
> diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
> index e001e6b..3666384 100644
> --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
> @@ -75,11 +75,16 @@ const struct mdp5_cfg_hw msm8x74_config = {
>                 .count = 4,
>                 .base = { 0x12500, 0x12700, 0x12900, 0x12b00 },
>         },
> +       .wb = {
> +               .count = 5,
> +               .base = { 0x11100, 0x13100, 0x15100, 0x17100, 0x19100 },
> +       },
>         .intfs = {
>                 [0] = INTF_eDP,
>                 [1] = INTF_DSI,
>                 [2] = INTF_DSI,
>                 [3] = INTF_HDMI,
> +               [4] = INTF_WB,
>         },
>         .max_clk = 200000000,
>  };
> @@ -145,11 +150,16 @@ const struct mdp5_cfg_hw apq8084_config = {
>                 .count = 5,
>                 .base = { 0x12500, 0x12700, 0x12900, 0x12b00, 0x12d00 },
>         },
> +       .wb = {
> +               .count = 5,
> +               .base = { 0x11100, 0x11500, 0x11900, 0x11d00, 0x12100 },
> +       },
>         .intfs = {
>                 [0] = INTF_eDP,
>                 [1] = INTF_DSI,
>                 [2] = INTF_DSI,
>                 [3] = INTF_HDMI,
> +               [4] = INTF_WB,
>         },
>         .max_clk = 320000000,
>  };
> diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h
> index 3a551b0..4834cdb 100644
> --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h
> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h
> @@ -73,6 +73,7 @@ struct mdp5_cfg_hw {
>         struct mdp5_sub_block ad;
>         struct mdp5_sub_block pp;
>         struct mdp5_sub_block intf;
> +       struct mdp5_sub_block wb;
>
>         u32 intfs[MDP5_INTF_NUM_MAX]; /* array of enum mdp5_intf_type */
>
> diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c
> index dfa8beb..e6e8817 100644
> --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c
> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c
> @@ -187,7 +187,9 @@ static struct drm_encoder *construct_encoder(struct mdp5_kms *mdp5_kms,
>                         .mode   = intf_mode,
>         };
>
> -       if ((intf_type == INTF_DSI) &&
> +       if (intf_type == INTF_WB)
> +               encoder = mdp5_wb_encoder_init(dev, &intf);
> +       else if ((intf_type == INTF_DSI) &&
>                 (intf_mode == MDP5_INTF_DSI_MODE_COMMAND))
>                 encoder = mdp5_cmd_encoder_init(dev, &intf);
>         else
> @@ -293,6 +295,19 @@ static int modeset_init_intf(struct mdp5_kms *mdp5_kms, int intf_num)
>                 ret = msm_dsi_modeset_init(priv->dsi[dsi_id], dev, dsi_encs);
>                 break;
>         }
> +       case INTF_WB:
> +               if (!priv->wb)
> +                       break;
> +
> +               encoder = construct_encoder(mdp5_kms, INTF_WB, intf_num,
> +                                       MDP5_INTF_WB_MODE_LINE);
> +               if (IS_ERR(encoder)) {
> +                       ret = PTR_ERR(encoder);
> +                       break;
> +               }
> +
> +               ret = msm_wb_modeset_init(priv->wb, dev, encoder);
> +               break;
>         default:
>                 dev_err(dev->dev, "unknown intf: %d\n", intf_type);
>                 ret = -EINVAL;
> diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
> index 2c0de17..680c81f 100644
> --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
> @@ -263,4 +263,12 @@ static inline int mdp5_cmd_encoder_set_split_display(
>  }
>  #endif
>
> +#ifdef CONFIG_DRM_MSM_WB
> +struct drm_encoder *mdp5_wb_encoder_init(struct drm_device *dev,
> +               struct mdp5_interface *intf);
> +#else
> +static inline struct drm_encoder *mdp5_wb_encoder_init(struct drm_device *dev,
> +               struct mdp5_interface *intf) { return ERR_PTR(-EINVAL); }
> +#endif
> +
>  #endif /* __MDP5_KMS_H__ */
> diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_wb_encoder.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_wb_encoder.c
> new file mode 100644
> index 0000000..55c9ccd
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_wb_encoder.c
> @@ -0,0 +1,466 @@
> +/* Copyright (c) 2015, 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.
> + */
> +
> +#include "mdp5_kms.h"
> +#include "mdp_wb.h"
> +
> +#include "drm_crtc.h"
> +#include "drm_crtc_helper.h"
> +
> +struct mdp5_wb_encoder {
> +       struct drm_encoder base;
> +       struct mdp5_interface intf;
> +       bool enabled;
> +       uint32_t bsc;
> +       struct mdp5_ctl *ctl;
> +
> +       /* irq handler for wb encoder */
> +       struct mdp_irq wb_vblank;
> +       /* wb id same as ctl id */
> +       u32 wb_id;
> +};
> +#define to_mdp5_wb_encoder(x) container_of(x, struct mdp5_wb_encoder, base)
> +
> +static struct mdp5_kms *get_kms(struct drm_encoder *encoder)
> +{
> +       struct msm_drm_private *priv = encoder->dev->dev_private;
> +
> +       return to_mdp5_kms(to_mdp_kms(priv->kms));
> +}
> +
> +static struct msm_wb *get_wb(struct drm_encoder *encoder)
> +{
> +       struct msm_drm_private *priv = encoder->dev->dev_private;
> +
> +       return priv->wb;
> +}
> +
> +#ifdef CONFIG_MSM_BUS_SCALING
> +#include <mach/board.h>
> +#include <linux/msm-bus.h>
> +#include <linux/msm-bus-board.h>
> +#define MDP_BUS_VECTOR_ENTRY(ab_val, ib_val)           \
> +       {                                               \
> +               .src = MSM_BUS_MASTER_MDP_PORT0,        \
> +               .dst = MSM_BUS_SLAVE_EBI_CH0,           \
> +               .ab = (ab_val),                         \
> +               .ib = (ib_val),                         \
> +       }
> +
> +static struct msm_bus_vectors mdp_bus_vectors[] = {
> +       MDP_BUS_VECTOR_ENTRY(0, 0),
> +       MDP_BUS_VECTOR_ENTRY(2000000000, 2000000000),
> +};
> +static struct msm_bus_paths mdp_bus_usecases[] = {
> +       {
> +               .num_paths = 1,
> +               .vectors = &mdp_bus_vectors[0],
> +       },
> +       {
> +               .num_paths = 1,
> +               .vectors = &mdp_bus_vectors[1],
> +       }
> +};
> +static struct msm_bus_scale_pdata mdp_bus_scale_table = {
> +       .usecase = mdp_bus_usecases,
> +       .num_usecases = ARRAY_SIZE(mdp_bus_usecases),
> +       .name = "mdss_mdp",
> +};
> +
> +static void bs_init(struct mdp5_wb_encoder *mdp5_wb_encoder)
> +{
> +       mdp5_wb_encoder->bsc = msm_bus_scale_register_client(
> +                       &mdp_bus_scale_table);
> +       DBG("bus scale client: %08x", mdp5_wb_encoder->bsc);
> +}
> +
> +static void bs_fini(struct mdp5_wb_encoder *mdp5_wb_encoder)
> +{
> +       if (mdp5_wb_encoder->bsc) {
> +               msm_bus_scale_unregister_client(mdp5_wb_encoder->bsc);
> +               mdp5_wb_encoder->bsc = 0;
> +       }
> +}
> +
> +static void bs_set(struct mdp5_wb_encoder *mdp5_wb_encoder, int idx)
> +{
> +       if (mdp5_wb_encoder->bsc) {
> +               DBG("set bus scaling: %d", idx);
> +               /* HACK: scaling down, and then immediately back up
> +                * seems to leave things broken (underflow).. so
> +                * never disable:
> +                */
> +               idx = 1;
> +               msm_bus_scale_client_update_request(mdp5_wb_encoder->bsc, idx);
> +       }
> +}
> +#else
> +static void bs_init(struct mdp5_wb_encoder *mdp5_wb_encoder) {}
> +static void bs_fini(struct mdp5_wb_encoder *mdp5_wb_encoder) {}
> +static void bs_set(struct mdp5_wb_encoder *mdp5_wb_encoder, int idx) {}
> +#endif
> +
> +static void mdp5_wb_encoder_destroy(struct drm_encoder *encoder)
> +{
> +       struct mdp5_wb_encoder *mdp5_wb_encoder = to_mdp5_wb_encoder(encoder);
> +
> +       bs_fini(mdp5_wb_encoder);
> +       drm_encoder_cleanup(encoder);
> +       kfree(mdp5_wb_encoder);
> +}
> +
> +static const struct drm_encoder_funcs mdp5_wb_encoder_funcs = {
> +       .destroy = mdp5_wb_encoder_destroy,
> +};
> +
> +static bool mdp5_wb_encoder_mode_fixup(struct drm_encoder *encoder,
> +               const struct drm_display_mode *mode,
> +               struct drm_display_mode *adjusted_mode)
> +{
> +       return true;
> +}
> +
> +void mdp5_wb_encoder_buf_prepare(struct msm_wb *wb, struct msm_wb_buffer *buf)
> +{
> +       struct drm_encoder *encoder = wb->encoder;
> +       struct mdp5_kms *mdp5_kms = get_kms(encoder);
> +       uint32_t nplanes = drm_format_num_planes(buf->pixel_format);
> +       int i;
> +
> +       DBG("plane no %d", nplanes);
> +       mdp5_enable(mdp5_kms);
> +       for (i = 0; i < nplanes; i++) {
> +               DBG("buf %d: plane %x", i, (int)buf->planes[i]);
> +               msm_gem_get_iova(buf->planes[i], mdp5_kms->id, &buf->iova[i]);
> +               buf->iova[i] += buf->offsets[i];
> +       }
> +       for (; i < MAX_PLANE; i++)
> +               buf->iova[i] = 0;
> +       mdp5_disable(mdp5_kms);
> +}
> +
> +static void mdp5_wb_encoder_addr_setup(struct drm_encoder *encoder,
> +       struct msm_wb_buffer *buf)
> +{
> +       struct mdp5_wb_encoder *mdp5_wb_encoder = to_mdp5_wb_encoder(encoder);
> +       struct mdp5_kms *mdp5_kms = get_kms(encoder);
> +       u32 wb_id = mdp5_wb_encoder->wb_id;
> +
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_DST0_ADDR(wb_id), buf->iova[0]);
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_DST1_ADDR(wb_id), buf->iova[1]);
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_DST2_ADDR(wb_id), buf->iova[2]);
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_DST3_ADDR(wb_id), buf->iova[3]);
> +       DBG("Program WB DST address %x %x %x %x", buf->iova[0],
> +               buf->iova[1], buf->iova[2], buf->iova[3]);
> +       /* Notify ctl that wb buffer is ready to trigger start */
> +       mdp5_ctl_commit(mdp5_wb_encoder->ctl,
> +               mdp_ctl_flush_mask_encoder(&mdp5_wb_encoder->intf));
> +}
> +
> +static void wb_csc_setup(struct mdp5_kms *mdp5_kms, u32 wb_id,
> +               struct csc_cfg *csc)
> +{
> +       uint32_t  i;
> +       uint32_t *matrix;
> +
> +       if (unlikely(!csc))
> +               return;
> +
> +       matrix = csc->matrix;
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_0(wb_id),
> +                       MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11(matrix[0]) |
> +                       MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12(matrix[1]));
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_1(wb_id),
> +                       MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13(matrix[2]) |
> +                       MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21(matrix[3]));
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_2(wb_id),
> +                       MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22(matrix[4]) |
> +                       MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23(matrix[5]));
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_3(wb_id),
> +                       MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31(matrix[6]) |
> +                       MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32(matrix[7]));
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_4(wb_id),
> +                       MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33(matrix[8]));
> +
> +       for (i = 0; i < ARRAY_SIZE(csc->pre_bias); i++) {
> +               uint32_t *pre_clamp = csc->pre_clamp;
> +               uint32_t *post_clamp = csc->post_clamp;
> +
> +               mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PRECLAMP(wb_id, i),
> +                       MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH(pre_clamp[2*i+1]) |
> +                       MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW(pre_clamp[2*i]));
> +
> +               mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTCLAMP(wb_id, i),
> +                       MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH(post_clamp[2*i+1]) |
> +                       MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW(post_clamp[2*i]));
> +
> +               mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PREBIAS(wb_id, i),
> +                       MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE(csc->pre_bias[i]));
> +
> +               mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTBIAS(wb_id, i),
> +                       MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE(csc->post_bias[i]));
> +       }
> +}
> +
> +static void mdp5_wb_encoder_mode_set(struct drm_encoder *encoder,
> +               struct drm_display_mode *mode,
> +               struct drm_display_mode *adjusted_mode)
> +{
> +       struct mdp5_wb_encoder *mdp5_wb_encoder = to_mdp5_wb_encoder(encoder);
> +       struct mdp5_kms *mdp5_kms = get_kms(encoder);
> +       struct msm_kms *kms = &mdp5_kms->base.base;
> +       const struct msm_format *msm_fmt;
> +       const struct mdp_format *fmt;
> +       struct msm_wb *wb = get_wb(encoder);
> +       struct msm_wb_buf_format *wb_buf_fmt;
> +       struct msm_wb_buffer *buf;
> +       u32 wb_id;
> +       u32 dst_format, pattern, ystride0, ystride1, outsize, chroma_samp;
> +       u32 opmode = 0;
> +
> +       DBG("Wb2 encoder modeset");
> +
> +       /* now we can get the ctl from crtc and extract the wb_id from ctl */
> +       if (!mdp5_wb_encoder->ctl)
> +               mdp5_wb_encoder->ctl = mdp5_crtc_get_ctl(encoder->crtc);
> +
> +       wb_id = mdp5_ctl_get_ctl_id(mdp5_wb_encoder->ctl);
> +       mdp5_wb_encoder->wb_id = wb_id;
> +
> +       /* get color_format from wb device */
> +       wb_buf_fmt = msm_wb_get_buf_format(wb);
> +       msm_fmt = kms->funcs->get_format(kms, wb_buf_fmt->pixel_format);
> +       if (!msm_fmt) {
> +               pr_err("%s: Unsupported Color Format %d\n", __func__,
> +                       wb_buf_fmt->pixel_format);
> +               return;
> +       }
> +
> +       fmt = to_mdp_format(msm_fmt);
> +       chroma_samp = fmt->chroma_sample;
> +
> +       if (MDP_FORMAT_IS_YUV(fmt)) {
> +               /*  config csc */
> +               DBG("YUV output %d, configure CSC",
> +                       fmt->base.pixel_format);
> +               wb_csc_setup(mdp5_kms, mdp5_wb_encoder->wb_id,
> +                       mdp_get_default_csc_cfg(CSC_RGB2YUV));
> +               opmode |= MDP5_WB_DST_OP_MODE_CSC_EN |
> +                       MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT(
> +                               DATA_FORMAT_RGB) |
> +                       MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT(
> +                               DATA_FORMAT_YUV);
> +
> +               switch (chroma_samp) {
> +               case CHROMA_420:
> +               case CHROMA_H2V1:
> +                       opmode |= MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_EN;
> +                       break;
> +               case CHROMA_H1V2:
> +               default:
> +                       pr_err("unsupported wb chroma samp=%d\n", chroma_samp);
> +                       return;
> +               }
> +       }
> +
> +       dst_format = MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP(chroma_samp) |
> +               MDP5_WB_DST_FORMAT_WRITE_PLANES(fmt->fetch_type) |
> +               MDP5_WB_DST_FORMAT_DSTC3_OUT(fmt->bpc_a) |
> +               MDP5_WB_DST_FORMAT_DSTC2_OUT(fmt->bpc_r) |
> +               MDP5_WB_DST_FORMAT_DSTC1_OUT(fmt->bpc_b) |
> +               MDP5_WB_DST_FORMAT_DSTC0_OUT(fmt->bpc_g) |
> +               COND(fmt->unpack_tight, MDP5_WB_DST_FORMAT_PACK_TIGHT) |
> +               MDP5_WB_DST_FORMAT_PACK_COUNT(fmt->unpack_count - 1) |
> +               MDP5_WB_DST_FORMAT_DST_BPP(fmt->cpp - 1);
> +
> +       if (fmt->bpc_a || fmt->alpha_enable) {
> +               dst_format |= MDP5_WB_DST_FORMAT_DSTC3_EN;
> +               if (!fmt->alpha_enable)
> +                       dst_format |= MDP5_WB_DST_FORMAT_DST_ALPHA_X;
> +       }
> +
> +       pattern = MDP5_WB_DST_PACK_PATTERN_ELEMENT3(fmt->unpack[3]) |
> +               MDP5_WB_DST_PACK_PATTERN_ELEMENT2(fmt->unpack[2]) |
> +               MDP5_WB_DST_PACK_PATTERN_ELEMENT1(fmt->unpack[1]) |
> +               MDP5_WB_DST_PACK_PATTERN_ELEMENT0(fmt->unpack[0]);
> +
> +       /* get the stride info from WB device */
> +       ystride0 = MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE(wb_buf_fmt->pitches[0]) |
> +               MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE(wb_buf_fmt->pitches[1]);
> +       ystride1 = MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE(wb_buf_fmt->pitches[2]) |
> +               MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE(wb_buf_fmt->pitches[3]);
> +
> +       /* get the output resolution from WB device */
> +       outsize = MDP5_WB_OUT_SIZE_DST_H(wb_buf_fmt->height) |
> +               MDP5_WB_OUT_SIZE_DST_W(wb_buf_fmt->width);
> +
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_ALPHA_X_VALUE(wb_id), 0xFF);
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_DST_FORMAT(wb_id), dst_format);
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_DST_OP_MODE(wb_id), opmode);
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_DST_PACK_PATTERN(wb_id), pattern);
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE0(wb_id), ystride0);
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE1(wb_id), ystride1);
> +       mdp5_write(mdp5_kms, REG_MDP5_WB_OUT_SIZE(wb_id), outsize);
> +
> +       mdp5_crtc_set_intf(encoder->crtc, &mdp5_wb_encoder->intf);
> +
> +       /* program the dst address */
> +       buf = msm_wb_dequeue_buf(wb, MSM_WB_BUF_Q_FREE);
> +       /*
> +        * if no free buffer is available, the only possibility is
> +        * WB connector becomes offline. User app should be notified
> +        * by udev event and stop the rendering soon.
> +        * so don't do anything here.
> +        */
> +       if (!buf) {
> +               pr_warn("%s: No buffer available\n", __func__);
> +               return;
> +       }
> +
> +       /* Last step of mode set: set up dst address */
> +       msm_wb_queue_buf(wb, buf, MSM_WB_BUF_Q_ACTIVE);
> +       mdp5_wb_encoder_addr_setup(encoder, buf);
> +}
> +
> +static void mdp5_wb_encoder_disable(struct drm_encoder *encoder)
> +{
> +       struct mdp5_wb_encoder *mdp5_wb_encoder = to_mdp5_wb_encoder(encoder);
> +       struct mdp5_kms *mdp5_kms = get_kms(encoder);
> +       struct mdp5_ctl *ctl = mdp5_crtc_get_ctl(encoder->crtc);
> +       struct msm_wb *wb = get_wb(encoder);
> +       struct msm_wb_buffer *buf;
> +
> +       DBG("Disable wb encoder");
> +
> +       if (WARN_ON(!mdp5_wb_encoder->enabled))
> +               return;
> +
> +       mdp5_ctl_set_encoder_state(ctl, false);
> +
> +       mdp_irq_unregister(&mdp5_kms->base,
> +               &mdp5_wb_encoder->wb_vblank);
> +
> +       /* move the active buf to free buf queue*/
> +       while ((buf = msm_wb_dequeue_buf(wb, MSM_WB_BUF_Q_ACTIVE))
> +               != NULL)
> +               msm_wb_queue_buf(wb, buf, MSM_WB_BUF_Q_FREE);
> +
> +       msm_wb_update_encoder_state(wb, false);
> +       bs_set(mdp5_wb_encoder, 0);
> +
> +       mdp5_wb_encoder->enabled = false;
> +}
> +
> +static void mdp5_wb_encoder_enable(struct drm_encoder *encoder)
> +{
> +       struct mdp5_wb_encoder *mdp5_wb_encoder = to_mdp5_wb_encoder(encoder);
> +       struct mdp5_kms *mdp5_kms = get_kms(encoder);
> +       struct mdp5_ctl *ctl = mdp5_crtc_get_ctl(encoder->crtc);
> +       struct msm_wb *wb = get_wb(encoder);
> +
> +       DBG("Enable wb encoder");
> +
> +       if (WARN_ON(mdp5_wb_encoder->enabled))
> +               return;
> +
> +       bs_set(mdp5_wb_encoder, 1);
> +       mdp_irq_register(&mdp5_kms->base,
> +               &mdp5_wb_encoder->wb_vblank);
> +
> +
> +       mdp5_ctl_set_encoder_state(ctl, true);
> +       msm_wb_update_encoder_state(wb, true);
> +
> +       mdp5_wb_encoder->enabled = true;
> +}
> +
> +static const struct drm_encoder_helper_funcs mdp5_wb_encoder_helper_funcs = {
> +       .mode_fixup = mdp5_wb_encoder_mode_fixup,
> +       .mode_set = mdp5_wb_encoder_mode_set,
> +       .disable = mdp5_wb_encoder_disable,
> +       .enable = mdp5_wb_encoder_enable,
> +};
> +
> +static void mdp5_wb_encoder_vblank_irq(struct mdp_irq *irq, uint32_t irqstatus)
> +{
> +       struct mdp5_wb_encoder *mdp5_wb_encoder =
> +               container_of(irq, struct mdp5_wb_encoder, wb_vblank);
> +       struct mdp5_kms *mdp5_kms = get_kms(&mdp5_wb_encoder->base);
> +       struct msm_wb *wb = get_wb(&mdp5_wb_encoder->base);
> +       u32 wb_id = mdp5_wb_encoder->wb_id;
> +       struct msm_wb_buffer *new_buf, *buf;
> +       u32 reg_val;
> +
> +       DBG("wb id %d", wb_id);
> +
> +       reg_val = mdp5_read(mdp5_kms, REG_MDP5_WB_DST0_ADDR(wb_id));
> +       buf = msm_wb_dequeue_buf(wb, MSM_WB_BUF_Q_ACTIVE);
> +       if (WARN_ON(!buf || (reg_val != buf->iova[0]))) {
> +               if (!buf)
> +                       pr_err("%s: no active buffer\n", __func__);
> +               else
> +                       pr_err("%s: current addr %x expect %x\n",
> +                               __func__, reg_val, buf->iova[0]);
> +               return;
> +       }
> +
> +       /* retrieve the free buffer */
> +       new_buf = msm_wb_dequeue_buf(wb, MSM_WB_BUF_Q_FREE);
> +       if (!new_buf) {
> +               pr_info("%s: No buffer is available\n", __func__);
> +               /* reuse current active buffer */
> +               new_buf = buf;
> +       } else {
> +               msm_wb_buf_captured(wb, buf, false);
> +       }
> +
> +       /* Update the address anyway to trigger the WB flush */
> +       msm_wb_queue_buf(wb, new_buf, MSM_WB_BUF_Q_ACTIVE);
> +       mdp5_wb_encoder_addr_setup(&mdp5_wb_encoder->base, new_buf);
> +}
> +
> +/* initialize encoder */
> +struct drm_encoder *mdp5_wb_encoder_init(struct drm_device *dev,
> +                               struct mdp5_interface *intf)
> +{
> +       struct drm_encoder *encoder = NULL;
> +       struct mdp5_wb_encoder *mdp5_wb_encoder;
> +       int ret;
> +
> +       DBG("Init writeback encoder");
> +
> +       mdp5_wb_encoder = kzalloc(sizeof(*mdp5_wb_encoder), GFP_KERNEL);
> +       if (!mdp5_wb_encoder) {
> +               ret = -ENOMEM;
> +               goto fail;
> +       }
> +
> +       memcpy(&mdp5_wb_encoder->intf, intf, sizeof(mdp5_wb_encoder->intf));
> +       encoder = &mdp5_wb_encoder->base;
> +
> +       drm_encoder_init(dev, encoder, &mdp5_wb_encoder_funcs,
> +                DRM_MODE_ENCODER_VIRTUAL);
> +       drm_encoder_helper_add(encoder, &mdp5_wb_encoder_helper_funcs);
> +
> +       mdp5_wb_encoder->wb_vblank.irq = mdp5_wb_encoder_vblank_irq;
> +       mdp5_wb_encoder->wb_vblank.irqmask = intf2vblank(0, intf);
> +
> +       bs_init(mdp5_wb_encoder);
> +
> +       return encoder;
> +
> +fail:
> +       if (encoder)
> +               mdp5_wb_encoder_destroy(encoder);
> +
> +       return ERR_PTR(ret);
> +}
> diff --git a/drivers/gpu/drm/msm/mdp/mdp_kms.h b/drivers/gpu/drm/msm/mdp/mdp_kms.h
> index 5ae4039..2d3428c 100644
> --- a/drivers/gpu/drm/msm/mdp/mdp_kms.h
> +++ b/drivers/gpu/drm/msm/mdp/mdp_kms.h
> @@ -88,7 +88,7 @@ struct mdp_format {
>         uint8_t unpack[4];
>         bool alpha_enable, unpack_tight;
>         uint8_t cpp, unpack_count;
> -       enum mdp_sspp_fetch_type fetch_type;
> +       enum mdp_fetch_type fetch_type;
>         enum mdp_chroma_samp_type chroma_sample;
>  };
>  #define to_mdp_format(x) container_of(x, struct mdp_format, base)
> diff --git a/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.c b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.c
> new file mode 100644
> index 0000000..d9fc633
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.c
> @@ -0,0 +1,311 @@
> +/* Copyright (c) 2015, 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.
> + */
> +
> +#include "mdp_wb.h"
> +#include "msm_kms.h"
> +#include "../mdp_kms.h"
> +
> +struct msm_wb_priv_data {
> +       bool streaming;
> +
> +       struct msm_wb_buf_format fmt;
> +       /* buf queue */
> +       struct msm_wb_buf_queue vidq;
> +       spinlock_t vidq_lock;
> +
> +       /* wait queue to sync between v4l2 and drm during stream off */
> +       bool encoder_on;
> +       wait_queue_head_t encoder_state_wq;
> +};
> +
> +void msm_wb_update_encoder_state(struct msm_wb *wb, bool enable)
> +{
> +       wb->priv_data->encoder_on = enable;
> +       wake_up_all(&wb->priv_data->encoder_state_wq);
> +}
> +
> +struct msm_wb_buf_format *msm_wb_get_buf_format(struct msm_wb *wb)
> +{
> +       return &wb->priv_data->fmt;
> +}
> +
> +int msm_wb_set_buf_format(struct msm_wb *wb, u32 pixel_fmt,
> +               u32 width, u32 height)
> +{
> +       struct msm_drm_private *priv = wb->dev->dev_private;
> +       struct msm_kms *kms = priv->kms;
> +       const struct msm_format *msm_fmt;
> +       const struct mdp_format *mdp_fmt;
> +       struct msm_wb_buf_format *fmt = &wb->priv_data->fmt;
> +
> +       msm_fmt = kms->funcs->get_format(kms, pixel_fmt);
> +       if (!msm_fmt) {
> +               pr_err("%s: Unsupported Color Format %d\n", __func__,
> +                       pixel_fmt);
> +               return -EINVAL;
> +       }
> +
> +       mdp_fmt = to_mdp_format(msm_fmt);
> +
> +       fmt->pixel_format = pixel_fmt;
> +       fmt->width = width;
> +       fmt->height = height;
> +       DBG("Set format %x width %d height %d", pixel_fmt, width, height);
> +
> +       switch (mdp_fmt->fetch_type) {
> +       case MDP_PLANE_INTERLEAVED:
> +               fmt->plane_num = 1;
> +               fmt->pitches[0] = width * mdp_fmt->cpp;
> +               break;
> +       case MDP_PLANE_PLANAR:
> +               fmt->plane_num = 3;
> +               fmt->pitches[0] = width;
> +               fmt->pitches[1] = width;
> +               fmt->pitches[2] = width;
> +               if (mdp_fmt->alpha_enable) {
> +                       fmt->plane_num = 4;
> +                       fmt->pitches[3] = width;
> +               }
> +               break;
> +       case MDP_PLANE_PSEUDO_PLANAR:
> +               fmt->plane_num = 2;
> +               fmt->pitches[0] = width;
> +               switch (mdp_fmt->chroma_sample) {
> +               case CHROMA_H2V1:
> +               case CHROMA_420:
> +                       fmt->pitches[1] = width/2;
> +                       break;
> +               case CHROMA_H1V2:
> +                       fmt->pitches[1] = width;
> +                       break;
> +               default:
> +                       pr_err("%s: Not supported fmt\n", __func__);
> +                       return -EINVAL;
> +               }
> +               break;
> +       }
> +
> +       return 0;
> +}
> +
> +void msm_wb_queue_buf(struct msm_wb *wb, struct msm_wb_buffer *wb_buf,
> +       enum msm_wb_buf_queue_type type)
> +{
> +       unsigned long flags;
> +       struct list_head *q;
> +
> +       if (type == MSM_WB_BUF_Q_FREE)
> +               q = &wb->priv_data->vidq.free;
> +       else
> +               q = &wb->priv_data->vidq.active;
> +
> +       if (type == MSM_WB_BUF_Q_FREE)
> +               mdp5_wb_encoder_buf_prepare(wb, wb_buf);
> +
> +       spin_lock_irqsave(&wb->priv_data->vidq_lock, flags);
> +       list_add_tail(&wb_buf->list, q);
> +       spin_unlock_irqrestore(&wb->priv_data->vidq_lock, flags);
> +}
> +
> +struct msm_wb_buffer *msm_wb_dequeue_buf(struct msm_wb *wb,
> +       enum msm_wb_buf_queue_type type)
> +{
> +       struct msm_wb_buffer *buf = NULL;
> +       unsigned long flags;
> +       struct list_head *q;
> +
> +       if (type == MSM_WB_BUF_Q_FREE)
> +               q = &wb->priv_data->vidq.free;
> +       else
> +               q = &wb->priv_data->vidq.active;
> +
> +       spin_lock_irqsave(&wb->priv_data->vidq_lock, flags);
> +       if (!list_empty(q)) {
> +               buf = list_entry(q->next,
> +                               struct msm_wb_buffer, list);
> +               list_del(&buf->list);
> +       }
> +       spin_unlock_irqrestore(&wb->priv_data->vidq_lock, flags);
> +
> +       return buf;
> +}
> +
> +int msm_wb_start_streaming(struct msm_wb *wb)
> +{
> +       if (wb->priv_data->streaming) {
> +               pr_err("%s: wb is streaming\n", __func__);
> +               return -EBUSY;
> +       }
> +
> +       DBG("Stream ON");
> +       wb->priv_data->streaming = true;
> +       msm_wb_connector_hotplug(wb, wb->priv_data->streaming);
> +
> +       return 0;
> +}
> +
> +int msm_wb_stop_streaming(struct msm_wb *wb)
> +{
> +       int rc;
> +       struct msm_wb_buffer *buf;
> +
> +       if (!wb->priv_data->streaming) {
> +               pr_info("%s: wb is not streaming\n", __func__);
> +               return -EINVAL;
> +       }
> +
> +       DBG("Stream off");
> +       wb->priv_data->streaming = false;
> +       msm_wb_connector_hotplug(wb, wb->priv_data->streaming);
> +
> +       /* wait until drm encoder off */
> +       rc = wait_event_timeout(wb->priv_data->encoder_state_wq,
> +               !wb->priv_data->encoder_on, 10 * HZ);
> +       if (!rc) {
> +               pr_err("%s: wait encoder off timeout\n", __func__);
> +               return -ETIMEDOUT;
> +       }
> +
> +       /* flush all active and free buffers */
> +       while ((buf = msm_wb_dequeue_buf(wb, MSM_WB_BUF_Q_ACTIVE)) != NULL)
> +               msm_wb_buf_captured(wb, buf, true);
> +
> +       while ((buf = msm_wb_dequeue_buf(wb, MSM_WB_BUF_Q_FREE)) != NULL)
> +               msm_wb_buf_captured(wb, buf, true);
> +
> +       DBG("Stream turned off");
> +
> +       return 0;
> +}
> +
> +int msm_wb_modeset_init(struct msm_wb *wb,
> +       struct drm_device *dev, struct drm_encoder *encoder)
> +{
> +       struct msm_drm_private *priv = dev->dev_private;
> +       int ret;
> +
> +       wb->dev = dev;
> +       wb->encoder = encoder;
> +
> +       wb->connector = msm_wb_connector_init(wb);
> +       if (IS_ERR(wb->connector)) {
> +               ret = PTR_ERR(wb->connector);
> +               dev_err(dev->dev, "failed to create WB connector: %d\n", ret);
> +               wb->connector = NULL;
> +               return ret;
> +       }
> +
> +       priv->connectors[priv->num_connectors++] = wb->connector;
> +
> +       return 0;
> +}
> +
> +static void msm_wb_destroy(struct msm_wb *wb)
> +{
> +       platform_set_drvdata(wb->pdev, NULL);
> +}
> +
> +static struct msm_wb *msm_wb_init(struct platform_device *pdev)
> +{
> +       struct msm_wb *wb = NULL;
> +
> +       wb = devm_kzalloc(&pdev->dev, sizeof(*wb), GFP_KERNEL);
> +       if (!wb)
> +               return ERR_PTR(-ENOMEM);
> +
> +       wb->pdev = pdev;
> +       wb->priv_data = devm_kzalloc(&pdev->dev, sizeof(*wb->priv_data),
> +               GFP_KERNEL);
> +       if (!wb->priv_data)
> +               return ERR_PTR(-ENOMEM);
> +
> +       if (msm_wb_v4l2_init(wb)) {
> +               pr_err("%s: wb v4l2 init failed\n", __func__);
> +               return ERR_PTR(-ENODEV);
> +       }
> +
> +       spin_lock_init(&wb->priv_data->vidq_lock);
> +       INIT_LIST_HEAD(&wb->priv_data->vidq.active);
> +       INIT_LIST_HEAD(&wb->priv_data->vidq.free);
> +       init_waitqueue_head(&wb->priv_data->encoder_state_wq);
> +
> +       platform_set_drvdata(pdev, wb);
> +
> +       return wb;
> +}
> +
> +static int msm_wb_bind(struct device *dev, struct device *master, void *data)
> +{
> +       struct drm_device *drm = dev_get_drvdata(master);
> +       struct msm_drm_private *priv = drm->dev_private;
> +       struct msm_wb *wb;
> +
> +       wb = msm_wb_init(to_platform_device(dev));
> +       if (IS_ERR(wb))
> +               return PTR_ERR(wb);
> +
> +       priv->wb = wb;
> +
> +       return 0;
> +}
> +
> +static void msm_wb_unbind(struct device *dev, struct device *master,
> +               void *data)
> +{
> +       struct drm_device *drm = dev_get_drvdata(master);
> +       struct msm_drm_private *priv = drm->dev_private;
> +
> +       if (priv->wb) {
> +               msm_wb_destroy(priv->wb);
> +               priv->wb = NULL;
> +       }
> +}
> +
> +static const struct component_ops msm_wb_ops = {
> +               .bind   = msm_wb_bind,
> +               .unbind = msm_wb_unbind,
> +};
> +
> +static int msm_wb_dev_probe(struct platform_device *pdev)
> +{
> +       return component_add(&pdev->dev, &msm_wb_ops);
> +}
> +
> +static int msm_wb_dev_remove(struct platform_device *pdev)
> +{
> +       component_del(&pdev->dev, &msm_wb_ops);
> +       return 0;
> +}
> +
> +static const struct of_device_id dt_match[] = {
> +       { .compatible = "qcom,mdss_wb"},
> +       {}
> +};
> +
> +static struct platform_driver msm_wb_driver = {
> +       .probe = msm_wb_dev_probe,
> +       .remove = msm_wb_dev_remove,
> +       .driver = {
> +               .name = "wb_msm",
> +               .of_match_table = dt_match,
> +       },
> +};
> +
> +void __init msm_wb_register(void)
> +{
> +       platform_driver_register(&msm_wb_driver);
> +}
> +
> +void __exit msm_wb_unregister(void)
> +{
> +       platform_driver_unregister(&msm_wb_driver);
> +}
> diff --git a/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.h b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.h
> new file mode 100644
> index 0000000..a970b00
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.h
> @@ -0,0 +1,98 @@
> +/* Copyright (c) 2015, 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 __MDP_WB_H__
> +#define __MDP_WB_H__
> +
> +#include <linux/platform_device.h>
> +#include "msm_kms.h"
> +
> +struct vb2_buffer;
> +
> +struct msm_wb_buffer {
> +       struct list_head list;
> +       struct drm_gem_object *planes[MAX_PLANE];
> +       u32 pixel_format;
> +       u32 offsets[MAX_PLANE];
> +       u32 iova[MAX_PLANE];
> +       struct vb2_buffer *vb; /* v4l2 buffer */
> +};
> +
> +struct msm_wb_buf_format {
> +       u32 pixel_format;
> +       u32 width;
> +       u32 height;
> +       u32 plane_num;
> +       u32 pitches[MAX_PLANE];
> +};
> +
> +enum msm_wb_buf_queue_type {
> +       MSM_WB_BUF_Q_FREE = 0,
> +       MSM_WB_BUF_Q_ACTIVE,
> +       MSM_WB_BUF_Q_NUM
> +};
> +
> +struct msm_wb_buf_queue {
> +       struct list_head free;
> +       struct list_head active;
> +};
> +
> +struct msm_wb_priv_data;
> +struct msm_wb {
> +       struct drm_device *dev;
> +       struct platform_device *pdev;
> +
> +       struct drm_connector *connector;
> +       struct drm_encoder *encoder;
> +
> +       void *wb_v4l2;
> +
> +       struct msm_wb_priv_data *priv_data;
> +};
> +
> +int msm_wb_start_streaming(struct msm_wb *wb);
> +int msm_wb_stop_streaming(struct msm_wb *wb);
> +void mdp5_wb_encoder_buf_prepare(struct msm_wb *wb, struct msm_wb_buffer *buf);
> +void msm_wb_connector_hotplug(struct msm_wb *wb, bool connected);
> +int msm_wb_set_buf_format(struct msm_wb *wb, u32 pixel_fmt,
> +       u32 width, u32 height);
> +
> +#ifdef CONFIG_DRM_MSM_WB
> +struct msm_wb_buf_format *msm_wb_get_buf_format(struct msm_wb *wb);
> +void msm_wb_queue_buf(struct msm_wb *wb, struct msm_wb_buffer *buf,
> +       enum msm_wb_buf_queue_type type);
> +struct msm_wb_buffer *msm_wb_dequeue_buf(struct msm_wb *wb,
> +       enum msm_wb_buf_queue_type type);
> +void msm_wb_update_encoder_state(struct msm_wb *wb, bool enable);
> +void msm_wb_buf_captured(struct msm_wb *wb, struct msm_wb_buffer *buf,
> +       bool discard);
> +#else
> +static inline struct msm_wb_buf_format *msm_wb_get_buf_format(
> +       struct msm_wb *wb) { return NULL; }
> +static inline void msm_wb_queue_buf(struct msm_wb *wb,
> +       struct msm_wb_buffer *buf, enum msm_wb_buf_queue_type type) {}
> +static inline struct msm_wb_buffer *msm_wb_dequeue_buf(struct msm_wb *wb,
> +       enum msm_wb_buf_queue_type type) { return NULL; }
> +static inline void msm_wb_update_encoder_state(struct msm_wb *wb,
> +       bool enable) {}
> +static inline void msm_wb_buf_captured(struct msm_wb *wb,
> +       struct msm_wb_buffer *buf, bool discard) {}
> +#endif
> +
> +int msm_wb_v4l2_init(struct msm_wb *wb);
> +
> +/*
> + * wb connector:
> + */
> +struct drm_connector *msm_wb_connector_init(struct msm_wb *wb);
> +
> +#endif /* __MDP_WB_H__ */
> diff --git a/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_connector.c b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_connector.c
> new file mode 100644
> index 0000000..814dec9
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_connector.c
> @@ -0,0 +1,157 @@
> +/* Copyright (c) 2015, 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.
> + */
> +
> +#include "mdp_wb.h"
> +
> +struct msm_wb_connector {
> +       struct drm_connector base;
> +       struct msm_wb *wb;
> +       struct work_struct hpd_work;
> +       bool connected;
> +};
> +#define to_wb_connector(x) container_of(x, struct msm_wb_connector, base)
> +
> +static enum drm_connector_status msm_wb_connector_detect(
> +               struct drm_connector *connector, bool force)
> +{
> +       struct msm_wb_connector *wb_connector = to_wb_connector(connector);
> +
> +       DBG("%s", wb_connector->connected ? "connected" : "disconnected");
> +       return wb_connector->connected ?
> +               connector_status_connected : connector_status_disconnected;
> +}
> +
> +static void msm_wb_hotplug_work(struct work_struct *work)
> +{
> +       struct msm_wb_connector *wb_connector =
> +               container_of(work, struct msm_wb_connector, hpd_work);
> +       struct drm_connector *connector = &wb_connector->base;
> +
> +       drm_kms_helper_hotplug_event(connector->dev);
> +}
> +
> +void msm_wb_connector_hotplug(struct msm_wb *wb, bool connected)
> +{
> +       struct drm_connector *connector = wb->connector;
> +       struct msm_wb_connector *wb_connector = to_wb_connector(connector);
> +       struct msm_drm_private *priv = connector->dev->dev_private;
> +
> +       wb_connector->connected = connected;
> +       queue_work(priv->wq, &wb_connector->hpd_work);
> +}
> +
> +static void msm_wb_connector_destroy(struct drm_connector *connector)
> +{
> +       struct msm_wb_connector *wb_connector = to_wb_connector(connector);
> +
> +       drm_connector_unregister(connector);
> +       drm_connector_cleanup(connector);
> +
> +       kfree(wb_connector);
> +}
> +
> +static int msm_wb_connector_get_modes(struct drm_connector *connector)
> +{
> +       struct msm_wb_connector *wb_connector = to_wb_connector(connector);
> +       struct msm_wb *wb = wb_connector->wb;
> +       struct msm_wb_buf_format *wb_buf_fmt;
> +       struct drm_display_mode *mode = NULL;
> +
> +       wb_buf_fmt = msm_wb_get_buf_format(wb);
> +       mode = drm_cvt_mode(connector->dev, wb_buf_fmt->width,
> +               wb_buf_fmt->height, 60, false, false, false);
> +
> +       if (!mode) {
> +               pr_err("%s: failed to create mode\n", __func__);
> +               return -ENOTSUPP;
> +       }
> +
> +       drm_mode_probed_add(connector, mode);
> +
> +       return 1;
> +}
> +
> +static int msm_wb_connector_mode_valid(struct drm_connector *connector,
> +                                struct drm_display_mode *mode)
> +{
> +       return 0;
> +}
> +
> +static struct drm_encoder *
> +msm_wb_connector_best_encoder(struct drm_connector *connector)
> +{
> +       struct msm_wb_connector *wb_connector = to_wb_connector(connector);
> +
> +       return wb_connector->wb->encoder;
> +}
> +
> +static const struct drm_connector_funcs msm_wb_connector_funcs = {
> +       .dpms = drm_helper_connector_dpms,
> +       .detect = msm_wb_connector_detect,
> +       .fill_modes = drm_helper_probe_single_connector_modes,
> +       .destroy = msm_wb_connector_destroy,
> +       .reset = drm_atomic_helper_connector_reset,
> +       .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +
> +};
> +
> +static const struct drm_connector_helper_funcs msm_wb_connector_helper_funcs = {
> +       .get_modes = msm_wb_connector_get_modes,
> +       .mode_valid = msm_wb_connector_mode_valid,
> +       .best_encoder = msm_wb_connector_best_encoder,
> +};
> +
> +/* initialize connector */
> +struct drm_connector *msm_wb_connector_init(struct msm_wb *wb)
> +{
> +       struct drm_connector *connector = NULL;
> +       struct msm_wb_connector *wb_connector;
> +       int ret;
> +
> +       wb_connector = kzalloc(sizeof(*wb_connector), GFP_KERNEL);
> +       if (!wb_connector) {
> +               ret = -ENOMEM;
> +               goto fail;
> +       }
> +
> +       wb_connector->wb = wb;
> +       connector = &wb_connector->base;
> +
> +       ret = drm_connector_init(wb->dev, connector, &msm_wb_connector_funcs,
> +                       DRM_MODE_CONNECTOR_VIRTUAL);
> +       if (ret)
> +               goto fail;
> +
> +       drm_connector_helper_add(connector, &msm_wb_connector_helper_funcs);
> +
> +       connector->polled = DRM_CONNECTOR_POLL_HPD;
> +
> +       connector->interlace_allowed = 0;
> +       connector->doublescan_allowed = 0;
> +
> +       drm_connector_register(connector);
> +
> +       ret = drm_mode_connector_attach_encoder(connector, wb->encoder);
> +       if (ret)
> +               goto fail;
> +
> +       INIT_WORK(&wb_connector->hpd_work, msm_wb_hotplug_work);
> +
> +       return connector;
> +
> +fail:
> +       if (connector)
> +               msm_wb_connector_destroy(connector);
> +
> +       return ERR_PTR(ret);
> +}
> diff --git a/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_v4l2.c b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_v4l2.c
> new file mode 100644
> index 0000000..3822f6c
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_v4l2.c
> @@ -0,0 +1,501 @@
> +/* Copyright (c) 2015, 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.
> + */
> +
> +#include <linux/videodev2.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-fh.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-common.h>
> +#include <media/videobuf2-core.h>
> +
> +#include "mdp_wb.h"
> +
> +#define MAX_WIDTH 2048
> +#define MAX_HEIGHT 2048
> +
> +struct msm_wb_fmt {
> +       const char *name;
> +       u32 fourcc;          /* v4l2 format id */
> +       u32 drm_fourcc;      /* drm format id */
> +       u8 depth;
> +       u8 plane_cnt;
> +       u32 plane_bpp[MAX_PLANE]; /* bit per pixel per plalne */
> +       bool  is_yuv;
> +};
> +
> +static const struct msm_wb_fmt formats[] = {
> +       {
> +               .name     = "Y/CbCr 4:2:0",
> +               .fourcc   = V4L2_PIX_FMT_NV12,
> +               .drm_fourcc = DRM_FORMAT_NV12,
> +               .depth    = 12,
> +               .plane_cnt = 2,
> +               .plane_bpp = {8, 4, 0, 0},
> +               .is_yuv   = true,
> +       },
> +       {
> +               .name     = "Y/CrCb 4:2:0",
> +               .fourcc   = V4L2_PIX_FMT_NV21,
> +               .drm_fourcc = DRM_FORMAT_NV21,
> +               .depth    = 12,
> +               .plane_cnt = 2,
> +               .plane_bpp = {8, 4, 0, 0},
> +               .is_yuv   = true,
> +       },
> +       {
> +               .name     = "RGB24",
> +               .fourcc   = V4L2_PIX_FMT_RGB24,
> +               .drm_fourcc = DRM_FORMAT_RGB888,
> +               .depth    = 24,
> +               .plane_cnt = 2,
> +               .plane_bpp = {24, 0, 0, 0},
> +       },
> +       {
> +               .name     = "ARGB32",
> +               .fourcc   = V4L2_PIX_FMT_RGB32,
> +               .drm_fourcc = DRM_FORMAT_ARGB8888,
> +               .depth    = 32,
> +               .plane_cnt = 1,
> +               .plane_bpp = {24, 0, 0, 0},
> +       },
> +};
> +
> +/* buffer for one video frame */
> +struct msm_wb_v4l2_buffer {
> +       /* common v4l buffer stuff -- must be first */
> +       struct vb2_buffer vb;
> +       struct msm_wb_buffer wb_buf;
> +};
> +
> +struct msm_wb_v4l2_dev {
> +       struct v4l2_device v4l2_dev;
> +       struct video_device vdev;
> +
> +       struct mutex mutex;
> +
> +       /* video capture */
> +       const struct msm_wb_fmt *fmt;
> +       unsigned int width, height;
> +
> +       struct vb2_queue vb_vidq;
> +
> +       struct msm_wb *wb;
> +};
> +
> +static const struct msm_wb_fmt *get_format(u32 fourcc)
> +{
> +       const struct msm_wb_fmt *fmt;
> +       unsigned int k;
> +
> +       for (k = 0; k < ARRAY_SIZE(formats); k++) {
> +               fmt = &formats[k];
> +               if (fmt->fourcc == fourcc)
> +                       return fmt;
> +       }
> +
> +       return NULL;
> +}
> +
> +void msm_wb_buf_captured(struct msm_wb *wb,
> +       struct msm_wb_buffer *buf, bool discard)
> +{
> +       struct msm_wb_v4l2_buffer *v4l2_buf =
> +               container_of(buf, struct msm_wb_v4l2_buffer, wb_buf);
> +       enum vb2_buffer_state buf_state = discard ? VB2_BUF_STATE_ERROR :
> +                       VB2_BUF_STATE_DONE;
> +
> +       v4l2_get_timestamp(&v4l2_buf->vb.v4l2_buf.timestamp);
> +       vb2_buffer_done(&v4l2_buf->vb, buf_state);
> +}
> +
> +/* ------------------------------------------------------------------
> +       DMA buffer operations
> +   ------------------------------------------------------------------*/
> +
> +static int msm_wb_vb2_map_dmabuf(void *mem_priv)
> +{
> +       return 0;
> +}
> +
> +static void msm_wb_vb2_unmap_dmabuf(void *mem_priv)
> +{
> +}
> +
> +static void *msm_wb_vb2_attach_dmabuf(void *alloc_ctx, struct dma_buf *dbuf,
> +       unsigned long size, int write)
> +{
> +       struct msm_wb_v4l2_dev *dev = alloc_ctx;
> +       struct drm_device *drm_dev = dev->wb->dev;
> +       struct drm_gem_object *obj;
> +
> +       obj = drm_dev->driver->gem_prime_import(drm_dev, dbuf);
> +       if (IS_ERR(obj)) {
> +               v4l2_err(&dev->v4l2_dev, "Can't convert dmabuf to gem obj.\n");
> +               goto out;
> +       }
> +
> +       if (obj->dma_buf) {
> +               if (WARN_ON(obj->dma_buf != dbuf)) {
> +                       v4l2_err(&dev->v4l2_dev,
> +                               "dma buf doesn't match.\n");
> +                       obj = ERR_PTR(-EINVAL);
> +               }
> +       } else {
> +               obj->dma_buf = dbuf;
> +       }
> +
> +out:
> +       return obj;
> +}
> +
> +static void msm_wb_vb2_detach_dmabuf(void *mem_priv)
> +{
> +       struct drm_gem_object *obj = mem_priv;
> +
> +       drm_gem_object_unreference_unlocked(obj);
> +}
> +
> +void *msm_wb_vb2_cookie(void *buf_priv)
> +{
> +       return buf_priv;
> +}
> +
> +const struct vb2_mem_ops msm_wb_vb2_mem_ops = {
> +       .map_dmabuf = msm_wb_vb2_map_dmabuf,
> +       .unmap_dmabuf = msm_wb_vb2_unmap_dmabuf,
> +       .attach_dmabuf = msm_wb_vb2_attach_dmabuf,
> +       .detach_dmabuf = msm_wb_vb2_detach_dmabuf,
> +       .cookie = msm_wb_vb2_cookie,
> +};
> +
> +/* ------------------------------------------------------------------
> +       Videobuf operations
> +   ------------------------------------------------------------------*/
> +#define MSM_WB_BUF_NUM_MIN 4
> +
> +static int msm_wb_vb2_queue_setup(struct vb2_queue *vq,
> +               const struct v4l2_format *fmt,
> +               unsigned int *nbuffers, unsigned int *nplanes,
> +               unsigned int sizes[], void *alloc_ctxs[])
> +{
> +       struct msm_wb_v4l2_dev *dev = vb2_get_drv_priv(vq);
> +       const struct msm_wb_fmt *wb_fmt = dev->fmt;
> +       int i;
> +
> +       *nbuffers = MSM_WB_BUF_NUM_MIN;
> +       *nplanes = wb_fmt->plane_cnt;
> +
> +       for (i = 0; i < *nplanes; i++) {
> +               sizes[i] = (wb_fmt->plane_bpp[i] * dev->width *
> +                       dev->height) >> 3;
> +               alloc_ctxs[i] = dev;
> +       }
> +
> +       v4l2_info(dev, "%s, count=%d, plane count=%d\n", __func__,
> +               *nbuffers, *nplanes);
> +
> +       return 0;
> +}
> +
> +static int msm_wb_vb2_buf_prepare(struct vb2_buffer *vb)
> +{
> +       return 0;
> +}
> +
> +static void msm_wb_vb2_buf_queue(struct vb2_buffer *vb)
> +{
> +       struct msm_wb_v4l2_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
> +       struct msm_wb_v4l2_buffer *buf =
> +               container_of(vb, struct msm_wb_v4l2_buffer, vb);
> +       struct msm_wb_buffer *wb_buf = &buf->wb_buf;
> +       int i;
> +
> +       /* pass the buffer to wb */
> +       wb_buf->vb = vb;
> +       wb_buf->pixel_format = dev->fmt->drm_fourcc;
> +       for (i = 0; i < vb->num_planes; i++) {
> +               wb_buf->offsets[i] = vb->v4l2_planes[i].data_offset;
> +               wb_buf->planes[i] = vb2_plane_cookie(vb, i);
> +               WARN_ON(!wb_buf->planes[i]);
> +       }
> +
> +       msm_wb_queue_buf(dev->wb, wb_buf, MSM_WB_BUF_Q_FREE);
> +}
> +
> +static int msm_wb_vb2_start_streaming(struct vb2_queue *vq, unsigned int count)
> +{
> +       struct msm_wb_v4l2_dev *dev = vb2_get_drv_priv(vq);
> +
> +       v4l2_info(dev, "%s\n", __func__);
> +
> +       return msm_wb_start_streaming(dev->wb);
> +}
> +
> +/* abort streaming and wait for last buffer */
> +static int msm_wb_vb2_stop_streaming(struct vb2_queue *vq)
> +{
> +       struct msm_wb_v4l2_dev *dev = vb2_get_drv_priv(vq);
> +
> +       v4l2_info(dev, "%s\n", __func__);
> +
> +       return msm_wb_stop_streaming(dev->wb);
> +}
> +
> +static const struct vb2_ops msm_wb_vb2_ops = {
> +       .queue_setup = msm_wb_vb2_queue_setup,
> +       .buf_prepare = msm_wb_vb2_buf_prepare,
> +       .buf_queue = msm_wb_vb2_buf_queue,
> +       .start_streaming = msm_wb_vb2_start_streaming,
> +       .stop_streaming = msm_wb_vb2_stop_streaming,
> +};
> +
> +/* ------------------------------------------------------------------
> +       IOCTL vidioc handling
> +   ------------------------------------------------------------------*/
> +static int msm_wb_vidioc_querycap(struct file *file, void  *priv,
> +                                       struct v4l2_capability *cap)
> +{
> +       struct msm_wb_v4l2_dev *dev = video_drvdata(file);
> +
> +       strcpy(cap->driver, "msm_wb");
> +       strcpy(cap->card, "msm_wb");
> +       snprintf(cap->bus_info, sizeof(cap->bus_info),
> +                       "platform:%s", dev->v4l2_dev.name);
> +       cap->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING;
> +       cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
> +
> +       return 0;
> +}
> +
> +static int msm_wb_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
> +                                       struct v4l2_fmtdesc *f)
> +{
> +       struct msm_wb_v4l2_dev *dev = video_drvdata(file);
> +       const struct msm_wb_fmt *fmt;
> +
> +       if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
> +               v4l2_err(&dev->v4l2_dev, "Invalid buf type %d.\n",
> +                       f->type);
> +               return -EINVAL;
> +       }
> +
> +       if (f->index >= ARRAY_SIZE(formats))
> +               return -ERANGE;
> +
> +       fmt = &formats[f->index];
> +
> +       strlcpy(f->description, fmt->name, sizeof(f->description));
> +       f->pixelformat = fmt->fourcc;
> +
> +       return 0;
> +}
> +
> +static int msm_wb_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
> +                                       struct v4l2_format *f)
> +{
> +       struct msm_wb_v4l2_dev *dev = video_drvdata(file);
> +       int i;
> +
> +       f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +       f->fmt.pix_mp.width        = dev->width;
> +       f->fmt.pix_mp.height       = dev->height;
> +       f->fmt.pix_mp.field        = V4L2_FIELD_NONE;
> +       f->fmt.pix_mp.pixelformat  = dev->fmt->fourcc;
> +       f->fmt.pix_mp.num_planes = dev->fmt->plane_cnt;
> +
> +       for (i = 0; i < dev->fmt->plane_cnt; i++) {
> +               f->fmt.pix_mp.plane_fmt[i].bytesperline =
> +                       (dev->fmt->plane_bpp[i] * dev->width) >> 3;
> +               f->fmt.pix_mp.plane_fmt[i].sizeimage =
> +                       f->fmt.pix_mp.plane_fmt[i].bytesperline * dev->height;
> +       }
> +
> +       if (dev->fmt->is_yuv)
> +               f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SMPTE170M;
> +       else
> +               f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SRGB;
> +
> +       return 0;
> +}
> +
> +static int msm_wb_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
> +                       struct v4l2_format *f)
> +{
> +       struct msm_wb_v4l2_dev *dev = video_drvdata(file);
> +       const struct msm_wb_fmt *fmt;
> +       int i;
> +
> +       if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
> +               v4l2_err(&dev->v4l2_dev, "Invalid buf type %d.\n",
> +                       f->type);
> +               return -EINVAL;
> +       }
> +
> +       fmt = get_format(f->fmt.pix_mp.pixelformat);
> +       if (!fmt) {
> +               v4l2_err(&dev->v4l2_dev, "Fourcc format (0x%08x) unknown.\n",
> +                       f->fmt.pix_mp.pixelformat);
> +               return -ENOTSUPP;
> +       }
> +
> +       f->fmt.pix_mp.field = V4L2_FIELD_NONE;
> +       v4l_bound_align_image(&f->fmt.pix_mp.width, 48, MAX_WIDTH, 4,
> +                             &f->fmt.pix_mp.height, 32, MAX_HEIGHT, 4, 0);
> +       f->fmt.pix_mp.num_planes = fmt->plane_cnt;
> +
> +       for (i = 0; i < dev->fmt->plane_cnt; i++) {
> +               f->fmt.pix_mp.plane_fmt[i].bytesperline =
> +                       (dev->fmt->plane_bpp[i] * f->fmt.pix_mp.width) >> 3;
> +               f->fmt.pix_mp.plane_fmt[i].sizeimage =
> +                       f->fmt.pix_mp.plane_fmt[i].bytesperline *
> +                       f->fmt.pix_mp.height;
> +       }
> +
> +       if (fmt->is_yuv)
> +               f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SMPTE170M;
> +       else
> +               f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SRGB;
> +
> +       return 0;
> +}
> +
> +static int msm_wb_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
> +                                       struct v4l2_format *f)
> +{
> +       struct msm_wb_v4l2_dev *dev = video_drvdata(file);
> +       struct msm_wb *wb = dev->wb;
> +       struct vb2_queue *q = &dev->vb_vidq;
> +       int rc;
> +
> +       rc = msm_wb_vidioc_try_fmt_vid_cap(file, priv, f);
> +       if (rc < 0)
> +               return rc;
> +
> +       if (vb2_is_busy(q)) {
> +               v4l2_err(&dev->v4l2_dev, "%s device busy\n", __func__);
> +               return -EBUSY;
> +       }
> +
> +       dev->fmt = get_format(f->fmt.pix_mp.pixelformat);
> +       dev->width = f->fmt.pix_mp.width;
> +       dev->height = f->fmt.pix_mp.height;
> +
> +       rc = msm_wb_set_buf_format(wb, dev->fmt->drm_fourcc,
> +               dev->width, dev->height);
> +       if (rc)
> +               v4l2_err(&dev->v4l2_dev,
> +                       "Set format (0x%08x w:%x h:%x) failed.\n",
> +                       dev->fmt->drm_fourcc, dev->width, dev->height);
> +
> +       return rc;
> +}
> +
> +static const struct v4l2_file_operations msm_wb_v4l2_fops = {
> +       .owner = THIS_MODULE,
> +       .open = v4l2_fh_open,

So one thing that I wanted sorting out before we let userspace see
streaming writeback (where I do think v4l is the right interface), is
a way to deal w/ permissions/security..  Ie. only the kms master
should control access to writeback.  Ie. an process that the
compositor isn't aware of / doesn't trust, should not be able to open
the v4l device and start snooping on the screen contents.  And I don't
think just file permissions in /dev is sufficient.  You likely don't
want to run your helper process doing video encode and streaming as a
privilaged user.

One way to handle this would be some sort of dri2 style
getmagic/authmagic sort of interface between the drm/kms master, and
v4l device, to unlock streaming.  But that is kind of passe.  Fd
passing is the fashionable thing now.  So instead we could use a dummy
v4l2_file_opererations::open() which always returns an error.  So v4l
device shows up in /dev.. but no userspace can open it.  And instead,
the way to get a fd for the v4l dev would be via a drm/kms ioctl (with
DRM_MASTER flag set).  Once compositor gets the fd, it can use fd
passing, if needed, to hand it off to a helper process, etc.

(probably use something like alloc_file() to get the 'struct file *',
then call directly into v4l2_fh_open(), and then get_unused_fd_flags()
+ fd_install() to get fd to return to userspace)

BR,
-R


> +       .release = vb2_fop_release,
> +       .poll = vb2_fop_poll,
> +       .unlocked_ioctl = video_ioctl2,
> +};
> +
> +static const struct v4l2_ioctl_ops msm_wb_v4l2_ioctl_ops = {
> +       .vidioc_querycap      = msm_wb_vidioc_querycap,
> +       .vidioc_enum_fmt_vid_cap_mplane = msm_wb_vidioc_enum_fmt_vid_cap,
> +       .vidioc_g_fmt_vid_cap_mplane = msm_wb_vidioc_g_fmt_vid_cap,
> +       .vidioc_try_fmt_vid_cap_mplane = msm_wb_vidioc_try_fmt_vid_cap,
> +       .vidioc_s_fmt_vid_cap_mplane = msm_wb_vidioc_s_fmt_vid_cap,
> +       .vidioc_reqbufs       = vb2_ioctl_reqbufs,
> +       .vidioc_querybuf      = vb2_ioctl_querybuf,
> +       .vidioc_qbuf          = vb2_ioctl_qbuf,
> +       .vidioc_dqbuf         = vb2_ioctl_dqbuf,
> +       .vidioc_streamon      = vb2_ioctl_streamon,
> +       .vidioc_streamoff     = vb2_ioctl_streamoff,
> +       .vidioc_log_status    = v4l2_ctrl_log_status,
> +       .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
> +       .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +static const struct video_device msm_wb_v4l2_template = {
> +       .name = "msm_wb",
> +       .fops = &msm_wb_v4l2_fops,
> +       .ioctl_ops = &msm_wb_v4l2_ioctl_ops,
> +       .release = video_device_release_empty,
> +};
> +
> +int msm_wb_v4l2_init(struct msm_wb *wb)
> +{
> +       struct msm_wb_v4l2_dev *dev;
> +       struct video_device *vfd;
> +       struct vb2_queue *q;
> +       int ret;
> +
> +       dev = kzalloc(sizeof(*dev), GFP_KERNEL);
> +       if (!dev)
> +               return -ENOMEM;
> +
> +       strncpy(dev->v4l2_dev.name, "msm_wb", sizeof(dev->v4l2_dev.name));
> +       ret = v4l2_device_register(NULL, &dev->v4l2_dev);
> +       if (ret)
> +               goto free_dev;
> +
> +       /* default ARGB8888 640x480 */
> +       dev->fmt = get_format(V4L2_PIX_FMT_RGB32);
> +       dev->width = 640;
> +       dev->height = 480;
> +
> +       /* initialize queue */
> +       q = &dev->vb_vidq;
> +       q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> +       q->io_modes = VB2_DMABUF;
> +       q->drv_priv = dev;
> +       q->buf_struct_size = sizeof(struct msm_wb_v4l2_buffer);
> +       q->ops = &msm_wb_vb2_ops;
> +       q->mem_ops = &msm_wb_vb2_mem_ops;
> +       q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +
> +       ret = vb2_queue_init(q);
> +       if (ret)
> +               goto unreg_dev;
> +
> +       mutex_init(&dev->mutex);
> +
> +       vfd = &dev->vdev;
> +       *vfd = msm_wb_v4l2_template;
> +       vfd->v4l2_dev = &dev->v4l2_dev;
> +       vfd->queue = q;
> +
> +       /*
> +        * Provide a mutex to v4l2 core. It will be used to protect
> +        * all fops and v4l2 ioctls.
> +        */
> +       vfd->lock = &dev->mutex;
> +       video_set_drvdata(vfd, dev);
> +
> +       ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1);
> +       if (ret < 0)
> +               goto unreg_dev;
> +
> +       dev->wb = wb;
> +       wb->wb_v4l2 = dev;
> +       v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",
> +                 video_device_node_name(vfd));
> +
> +       return 0;
> +
> +unreg_dev:
> +       v4l2_device_unregister(&dev->v4l2_dev);
> +free_dev:
> +       kfree(dev);
> +       return ret;
> +}
> diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
> index 47f4dd4..637c75d 100644
> --- a/drivers/gpu/drm/msm/msm_drv.c
> +++ b/drivers/gpu/drm/msm/msm_drv.c
> @@ -1076,6 +1076,7 @@ static struct platform_driver msm_platform_driver = {
>  static int __init msm_drm_register(void)
>  {
>         DBG("init");
> +       msm_wb_register();
>         msm_dsi_register();
>         msm_edp_register();
>         hdmi_register();
> @@ -1091,6 +1092,7 @@ static void __exit msm_drm_unregister(void)
>         adreno_unregister();
>         msm_edp_unregister();
>         msm_dsi_unregister();
> +       msm_wb_unregister();
>  }
>
>  module_init(msm_drm_register);
> diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
> index 04db4bd..423b666 100644
> --- a/drivers/gpu/drm/msm/msm_drv.h
> +++ b/drivers/gpu/drm/msm/msm_drv.h
> @@ -85,6 +85,8 @@ struct msm_drm_private {
>         /* DSI is shared by mdp4 and mdp5 */
>         struct msm_dsi *dsi[2];
>
> +       struct msm_wb *wb;
> +
>         /* when we have more than one 'msm_gpu' these need to be an array: */
>         struct msm_gpu *gpu;
>         struct msm_file_private *lastctx;
> @@ -265,6 +267,19 @@ static inline int msm_dsi_modeset_init(struct msm_dsi *msm_dsi,
>  }
>  #endif
>
> +struct msm_wb;
> +#ifdef CONFIG_DRM_MSM_WB
> +void __init msm_wb_register(void);
> +void __exit msm_wb_unregister(void);
> +int msm_wb_modeset_init(struct msm_wb *wb, struct drm_device *dev,
> +               struct drm_encoder *encoder);
> +#else
> +static inline void __init msm_wb_register(void) {}
> +static inline void __exit msm_wb_unregister(void) {}
> +static inline int msm_wb_modeset_init(struct msm_wb *wb, struct drm_device *dev,
> +               struct drm_encoder *encoder) { return -EINVAL; }
> +#endif
> +
>  #ifdef CONFIG_DEBUG_FS
>  void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m);
>  void msm_gem_describe_objects(struct list_head *list, struct seq_file *m);
> diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c
> index 95f6532..1a9ae28 100644
> --- a/drivers/gpu/drm/msm/msm_fbdev.c
> +++ b/drivers/gpu/drm/msm/msm_fbdev.c
> @@ -213,6 +213,38 @@ static void msm_crtc_fb_gamma_get(struct drm_crtc *crtc,
>         DBG("fbdev: get gamma");
>  }
>
> +/* add all connectors to fb except wb connector */
> +static int msm_drm_fb_add_connectors(struct drm_fb_helper *fb_helper)
> +{
> +       struct drm_device *dev = fb_helper->dev;
> +       struct drm_connector *connector;
> +       int i;
> +
> +       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
> +               struct drm_fb_helper_connector *fb_helper_connector;
> +
> +               if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL)
> +                       continue;
> +
> +               fb_helper_connector =
> +                       kzalloc(sizeof(*fb_helper_connector), GFP_KERNEL);
> +               if (!fb_helper_connector)
> +                       goto fail;
> +
> +               fb_helper_connector->connector = connector;
> +               fb_helper->connector_info[fb_helper->connector_count++] =
> +                       fb_helper_connector;
> +       }
> +       return 0;
> +fail:
> +       for (i = 0; i < fb_helper->connector_count; i++) {
> +               kfree(fb_helper->connector_info[i]);
> +               fb_helper->connector_info[i] = NULL;
> +       }
> +       fb_helper->connector_count = 0;
> +       return -ENOMEM;
> +}
> +
>  static const struct drm_fb_helper_funcs msm_fb_helper_funcs = {
>         .gamma_set = msm_crtc_fb_gamma_set,
>         .gamma_get = msm_crtc_fb_gamma_get,
> @@ -242,7 +274,7 @@ struct drm_fb_helper *msm_fbdev_init(struct drm_device *dev)
>                 goto fail;
>         }
>
> -       ret = drm_fb_helper_single_add_all_connectors(helper);
> +       ret = msm_drm_fb_add_connectors(helper);
>         if (ret)
>                 goto fini;
>
> --
> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
> a Linux Foundation Collaborative Project
>
Daniel Vetter Aug. 25, 2015, 7:05 a.m. UTC | #2
On Wed, Aug 19, 2015 at 03:00:04PM -0400, Rob Clark wrote:
> On Tue, Apr 7, 2015 at 2:09 PM, Jilai Wang <jilaiw@codeaurora.org> wrote:
> So one thing that I wanted sorting out before we let userspace see
> streaming writeback (where I do think v4l is the right interface), is
> a way to deal w/ permissions/security..  Ie. only the kms master
> should control access to writeback.  Ie. an process that the
> compositor isn't aware of / doesn't trust, should not be able to open
> the v4l device and start snooping on the screen contents.  And I don't
> think just file permissions in /dev is sufficient.  You likely don't
> want to run your helper process doing video encode and streaming as a
> privilaged user.
> 
> One way to handle this would be some sort of dri2 style
> getmagic/authmagic sort of interface between the drm/kms master, and
> v4l device, to unlock streaming.  But that is kind of passe.  Fd
> passing is the fashionable thing now.  So instead we could use a dummy
> v4l2_file_opererations::open() which always returns an error.  So v4l
> device shows up in /dev.. but no userspace can open it.  And instead,
> the way to get a fd for the v4l dev would be via a drm/kms ioctl (with
> DRM_MASTER flag set).  Once compositor gets the fd, it can use fd
> passing, if needed, to hand it off to a helper process, etc.
> 
> (probably use something like alloc_file() to get the 'struct file *',
> then call directly into v4l2_fh_open(), and then get_unused_fd_flags()
> + fd_install() to get fd to return to userspace)

Just following up here, but consensus from the lpc track is that we don't
need this as long as writeback is something which needs a specific action
to initiate. I.e. if we have a separate writeback connector which won't
grab any frames at all as long as it's disconnected we should be fine. Wrt
session handling that's something which would need to be fixed between
v4l and logind (or whatever).

In general I don't like hand-rolling our own proprietary v4l-open process,
it means that all the existing v4l test&dev tooling must be changed, even
when you're root.
-Daniel
Rob Clark Aug. 25, 2015, 7:11 p.m. UTC | #3
On Tue, Aug 25, 2015 at 3:05 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
> On Wed, Aug 19, 2015 at 03:00:04PM -0400, Rob Clark wrote:
>> On Tue, Apr 7, 2015 at 2:09 PM, Jilai Wang <jilaiw@codeaurora.org> wrote:
>> So one thing that I wanted sorting out before we let userspace see
>> streaming writeback (where I do think v4l is the right interface), is
>> a way to deal w/ permissions/security..  Ie. only the kms master
>> should control access to writeback.  Ie. an process that the
>> compositor isn't aware of / doesn't trust, should not be able to open
>> the v4l device and start snooping on the screen contents.  And I don't
>> think just file permissions in /dev is sufficient.  You likely don't
>> want to run your helper process doing video encode and streaming as a
>> privilaged user.
>>
>> One way to handle this would be some sort of dri2 style
>> getmagic/authmagic sort of interface between the drm/kms master, and
>> v4l device, to unlock streaming.  But that is kind of passe.  Fd
>> passing is the fashionable thing now.  So instead we could use a dummy
>> v4l2_file_opererations::open() which always returns an error.  So v4l
>> device shows up in /dev.. but no userspace can open it.  And instead,
>> the way to get a fd for the v4l dev would be via a drm/kms ioctl (with
>> DRM_MASTER flag set).  Once compositor gets the fd, it can use fd
>> passing, if needed, to hand it off to a helper process, etc.
>>
>> (probably use something like alloc_file() to get the 'struct file *',
>> then call directly into v4l2_fh_open(), and then get_unused_fd_flags()
>> + fd_install() to get fd to return to userspace)
>
> Just following up here, but consensus from the lpc track is that we don't
> need this as long as writeback is something which needs a specific action
> to initiate. I.e. if we have a separate writeback connector which won't
> grab any frames at all as long as it's disconnected we should be fine. Wrt
> session handling that's something which would need to be fixed between
> v4l and logind (or whatever).

Was that consensus?  I agree that something should initiate writeback
from the kms side of things.  But if we don't have *something* to
ensure whatever is on the other end of writeback is who we think it
is, it seems at least racy..

> In general I don't like hand-rolling our own proprietary v4l-open process,
> it means that all the existing v4l test&dev tooling must be changed, even
> when you're root.

well, I know that, for example, gst v4l2src allows you to pass in an
already opened v4l dev fd, which fits in pretty well with what I
propose.  The alternative, I think, is a dri2 style auth handshake
between drm/kms and v4l, which I am less thrilled about.

I would have to *assume* that userspace is at least prepared to deal
with -EPERM when it tries to open a device..  at least more so than
special auth ioctl sequence.

BR,
-R

> -Daniel
> --
> Daniel Vetter
> Software Engineer, Intel Corporation
> http://blog.ffwll.ch
Daniel Vetter Aug. 26, 2015, 12:06 p.m. UTC | #4
On Tue, Aug 25, 2015 at 9:11 PM, Rob Clark <robdclark@gmail.com> wrote:
> On Tue, Aug 25, 2015 at 3:05 AM, Daniel Vetter <daniel@ffwll.ch> wrote:
>> On Wed, Aug 19, 2015 at 03:00:04PM -0400, Rob Clark wrote:
>>> On Tue, Apr 7, 2015 at 2:09 PM, Jilai Wang <jilaiw@codeaurora.org> wrote:
>>> So one thing that I wanted sorting out before we let userspace see
>>> streaming writeback (where I do think v4l is the right interface), is
>>> a way to deal w/ permissions/security..  Ie. only the kms master
>>> should control access to writeback.  Ie. an process that the
>>> compositor isn't aware of / doesn't trust, should not be able to open
>>> the v4l device and start snooping on the screen contents.  And I don't
>>> think just file permissions in /dev is sufficient.  You likely don't
>>> want to run your helper process doing video encode and streaming as a
>>> privilaged user.
>>>
>>> One way to handle this would be some sort of dri2 style
>>> getmagic/authmagic sort of interface between the drm/kms master, and
>>> v4l device, to unlock streaming.  But that is kind of passe.  Fd
>>> passing is the fashionable thing now.  So instead we could use a dummy
>>> v4l2_file_opererations::open() which always returns an error.  So v4l
>>> device shows up in /dev.. but no userspace can open it.  And instead,
>>> the way to get a fd for the v4l dev would be via a drm/kms ioctl (with
>>> DRM_MASTER flag set).  Once compositor gets the fd, it can use fd
>>> passing, if needed, to hand it off to a helper process, etc.
>>>
>>> (probably use something like alloc_file() to get the 'struct file *',
>>> then call directly into v4l2_fh_open(), and then get_unused_fd_flags()
>>> + fd_install() to get fd to return to userspace)
>>
>> Just following up here, but consensus from the lpc track is that we don't
>> need this as long as writeback is something which needs a specific action
>> to initiate. I.e. if we have a separate writeback connector which won't
>> grab any frames at all as long as it's disconnected we should be fine. Wrt
>> session handling that's something which would need to be fixed between
>> v4l and logind (or whatever).
>
> Was that consensus?  I agree that something should initiate writeback
> from the kms side of things.  But if we don't have *something* to
> ensure whatever is on the other end of writeback is who we think it
> is, it seems at least racy..
>
>> In general I don't like hand-rolling our own proprietary v4l-open process,
>> it means that all the existing v4l test&dev tooling must be changed, even
>> when you're root.
>
> well, I know that, for example, gst v4l2src allows you to pass in an
> already opened v4l dev fd, which fits in pretty well with what I
> propose.  The alternative, I think, is a dri2 style auth handshake
> between drm/kms and v4l, which I am less thrilled about.
>
> I would have to *assume* that userspace is at least prepared to deal
> with -EPERM when it tries to open a device..  at least more so than
> special auth ioctl sequence.

Imo the right approach for that is to defer to logind. We already have
these issues, e.g. you probably don't want the wrong seat to be able
to access camera devices. Maybe no one bothered to support this yet,
but the problem is definitely there already. And with that broder
use-case of just making sure the right process can access the v4l
device nodes drm writeback is just another special case.

At least that's what I wanted to say at lpc, might have done a bad job
at phrasing it ;-)

DRM auth dance otoh is something entirely different and only needed
because we want multiple processes to use the same device. v4l doesn't
support that model yet at all, it really only needs some form of
revoke + logind adjusting permissions to match whatever the current
user is on a given seat.
-Daniel
diff mbox

Patch

diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig
index 0a6f676..5754d12 100644
--- a/drivers/gpu/drm/msm/Kconfig
+++ b/drivers/gpu/drm/msm/Kconfig
@@ -46,3 +46,13 @@  config DRM_MSM_DSI
 	  Choose this option if you have a need for MIPI DSI connector
 	  support.
 
+config DRM_MSM_WB
+	bool "Enable writeback support for MSM modesetting driver"
+	depends on DRM_MSM
+	depends on VIDEO_V4L2
+	select VIDEOBUF2_CORE
+	default y
+	help
+	  Choose this option if you have a need to support writeback
+	  connector.
+
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index ab20867..fd2b0bb 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -1,4 +1,5 @@ 
 ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/msm
+ccflags-$(CONFIG_DRM_MSM_WB) += -Idrivers/gpu/drm/msm/mdp/mdp_wb
 
 msm-y := \
 	adreno/adreno_device.o \
@@ -56,4 +57,10 @@  msm-$(CONFIG_DRM_MSM_DSI) += dsi/dsi.o \
 			dsi/dsi_phy.o \
 			mdp/mdp5/mdp5_cmd_encoder.o
 
+msm-$(CONFIG_DRM_MSM_WB) += \
+	mdp/mdp5/mdp5_wb_encoder.o \
+	mdp/mdp_wb/mdp_wb.o \
+	mdp/mdp_wb/mdp_wb_connector.o \
+	mdp/mdp_wb/mdp_wb_v4l2.o
+
 obj-$(CONFIG_DRM_MSM)	+= msm.o
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
index e001e6b..3666384 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
@@ -75,11 +75,16 @@  const struct mdp5_cfg_hw msm8x74_config = {
 		.count = 4,
 		.base = { 0x12500, 0x12700, 0x12900, 0x12b00 },
 	},
+	.wb = {
+		.count = 5,
+		.base = { 0x11100, 0x13100, 0x15100, 0x17100, 0x19100 },
+	},
 	.intfs = {
 		[0] = INTF_eDP,
 		[1] = INTF_DSI,
 		[2] = INTF_DSI,
 		[3] = INTF_HDMI,
+		[4] = INTF_WB,
 	},
 	.max_clk = 200000000,
 };
@@ -145,11 +150,16 @@  const struct mdp5_cfg_hw apq8084_config = {
 		.count = 5,
 		.base = { 0x12500, 0x12700, 0x12900, 0x12b00, 0x12d00 },
 	},
+	.wb = {
+		.count = 5,
+		.base = { 0x11100, 0x11500, 0x11900, 0x11d00, 0x12100 },
+	},
 	.intfs = {
 		[0] = INTF_eDP,
 		[1] = INTF_DSI,
 		[2] = INTF_DSI,
 		[3] = INTF_HDMI,
+		[4] = INTF_WB,
 	},
 	.max_clk = 320000000,
 };
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h
index 3a551b0..4834cdb 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h
@@ -73,6 +73,7 @@  struct mdp5_cfg_hw {
 	struct mdp5_sub_block ad;
 	struct mdp5_sub_block pp;
 	struct mdp5_sub_block intf;
+	struct mdp5_sub_block wb;
 
 	u32 intfs[MDP5_INTF_NUM_MAX]; /* array of enum mdp5_intf_type */
 
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c
index dfa8beb..e6e8817 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c
@@ -187,7 +187,9 @@  static struct drm_encoder *construct_encoder(struct mdp5_kms *mdp5_kms,
 			.mode	= intf_mode,
 	};
 
-	if ((intf_type == INTF_DSI) &&
+	if (intf_type == INTF_WB)
+		encoder = mdp5_wb_encoder_init(dev, &intf);
+	else if ((intf_type == INTF_DSI) &&
 		(intf_mode == MDP5_INTF_DSI_MODE_COMMAND))
 		encoder = mdp5_cmd_encoder_init(dev, &intf);
 	else
@@ -293,6 +295,19 @@  static int modeset_init_intf(struct mdp5_kms *mdp5_kms, int intf_num)
 		ret = msm_dsi_modeset_init(priv->dsi[dsi_id], dev, dsi_encs);
 		break;
 	}
+	case INTF_WB:
+		if (!priv->wb)
+			break;
+
+		encoder = construct_encoder(mdp5_kms, INTF_WB, intf_num,
+					MDP5_INTF_WB_MODE_LINE);
+		if (IS_ERR(encoder)) {
+			ret = PTR_ERR(encoder);
+			break;
+		}
+
+		ret = msm_wb_modeset_init(priv->wb, dev, encoder);
+		break;
 	default:
 		dev_err(dev->dev, "unknown intf: %d\n", intf_type);
 		ret = -EINVAL;
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
index 2c0de17..680c81f 100644
--- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
@@ -263,4 +263,12 @@  static inline int mdp5_cmd_encoder_set_split_display(
 }
 #endif
 
+#ifdef CONFIG_DRM_MSM_WB
+struct drm_encoder *mdp5_wb_encoder_init(struct drm_device *dev,
+		struct mdp5_interface *intf);
+#else
+static inline struct drm_encoder *mdp5_wb_encoder_init(struct drm_device *dev,
+		struct mdp5_interface *intf) { return ERR_PTR(-EINVAL); }
+#endif
+
 #endif /* __MDP5_KMS_H__ */
diff --git a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_wb_encoder.c b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_wb_encoder.c
new file mode 100644
index 0000000..55c9ccd
--- /dev/null
+++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_wb_encoder.c
@@ -0,0 +1,466 @@ 
+/* Copyright (c) 2015, 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.
+ */
+
+#include "mdp5_kms.h"
+#include "mdp_wb.h"
+
+#include "drm_crtc.h"
+#include "drm_crtc_helper.h"
+
+struct mdp5_wb_encoder {
+	struct drm_encoder base;
+	struct mdp5_interface intf;
+	bool enabled;
+	uint32_t bsc;
+	struct mdp5_ctl *ctl;
+
+	/* irq handler for wb encoder */
+	struct mdp_irq wb_vblank;
+	/* wb id same as ctl id */
+	u32 wb_id;
+};
+#define to_mdp5_wb_encoder(x) container_of(x, struct mdp5_wb_encoder, base)
+
+static struct mdp5_kms *get_kms(struct drm_encoder *encoder)
+{
+	struct msm_drm_private *priv = encoder->dev->dev_private;
+
+	return to_mdp5_kms(to_mdp_kms(priv->kms));
+}
+
+static struct msm_wb *get_wb(struct drm_encoder *encoder)
+{
+	struct msm_drm_private *priv = encoder->dev->dev_private;
+
+	return priv->wb;
+}
+
+#ifdef CONFIG_MSM_BUS_SCALING
+#include <mach/board.h>
+#include <linux/msm-bus.h>
+#include <linux/msm-bus-board.h>
+#define MDP_BUS_VECTOR_ENTRY(ab_val, ib_val)		\
+	{						\
+		.src = MSM_BUS_MASTER_MDP_PORT0,	\
+		.dst = MSM_BUS_SLAVE_EBI_CH0,		\
+		.ab = (ab_val),				\
+		.ib = (ib_val),				\
+	}
+
+static struct msm_bus_vectors mdp_bus_vectors[] = {
+	MDP_BUS_VECTOR_ENTRY(0, 0),
+	MDP_BUS_VECTOR_ENTRY(2000000000, 2000000000),
+};
+static struct msm_bus_paths mdp_bus_usecases[] = {
+	{
+		.num_paths = 1,
+		.vectors = &mdp_bus_vectors[0],
+	},
+	{
+		.num_paths = 1,
+		.vectors = &mdp_bus_vectors[1],
+	}
+};
+static struct msm_bus_scale_pdata mdp_bus_scale_table = {
+	.usecase = mdp_bus_usecases,
+	.num_usecases = ARRAY_SIZE(mdp_bus_usecases),
+	.name = "mdss_mdp",
+};
+
+static void bs_init(struct mdp5_wb_encoder *mdp5_wb_encoder)
+{
+	mdp5_wb_encoder->bsc = msm_bus_scale_register_client(
+			&mdp_bus_scale_table);
+	DBG("bus scale client: %08x", mdp5_wb_encoder->bsc);
+}
+
+static void bs_fini(struct mdp5_wb_encoder *mdp5_wb_encoder)
+{
+	if (mdp5_wb_encoder->bsc) {
+		msm_bus_scale_unregister_client(mdp5_wb_encoder->bsc);
+		mdp5_wb_encoder->bsc = 0;
+	}
+}
+
+static void bs_set(struct mdp5_wb_encoder *mdp5_wb_encoder, int idx)
+{
+	if (mdp5_wb_encoder->bsc) {
+		DBG("set bus scaling: %d", idx);
+		/* HACK: scaling down, and then immediately back up
+		 * seems to leave things broken (underflow).. so
+		 * never disable:
+		 */
+		idx = 1;
+		msm_bus_scale_client_update_request(mdp5_wb_encoder->bsc, idx);
+	}
+}
+#else
+static void bs_init(struct mdp5_wb_encoder *mdp5_wb_encoder) {}
+static void bs_fini(struct mdp5_wb_encoder *mdp5_wb_encoder) {}
+static void bs_set(struct mdp5_wb_encoder *mdp5_wb_encoder, int idx) {}
+#endif
+
+static void mdp5_wb_encoder_destroy(struct drm_encoder *encoder)
+{
+	struct mdp5_wb_encoder *mdp5_wb_encoder = to_mdp5_wb_encoder(encoder);
+
+	bs_fini(mdp5_wb_encoder);
+	drm_encoder_cleanup(encoder);
+	kfree(mdp5_wb_encoder);
+}
+
+static const struct drm_encoder_funcs mdp5_wb_encoder_funcs = {
+	.destroy = mdp5_wb_encoder_destroy,
+};
+
+static bool mdp5_wb_encoder_mode_fixup(struct drm_encoder *encoder,
+		const struct drm_display_mode *mode,
+		struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+void mdp5_wb_encoder_buf_prepare(struct msm_wb *wb, struct msm_wb_buffer *buf)
+{
+	struct drm_encoder *encoder = wb->encoder;
+	struct mdp5_kms *mdp5_kms = get_kms(encoder);
+	uint32_t nplanes = drm_format_num_planes(buf->pixel_format);
+	int i;
+
+	DBG("plane no %d", nplanes);
+	mdp5_enable(mdp5_kms);
+	for (i = 0; i < nplanes; i++) {
+		DBG("buf %d: plane %x", i, (int)buf->planes[i]);
+		msm_gem_get_iova(buf->planes[i], mdp5_kms->id, &buf->iova[i]);
+		buf->iova[i] += buf->offsets[i];
+	}
+	for (; i < MAX_PLANE; i++)
+		buf->iova[i] = 0;
+	mdp5_disable(mdp5_kms);
+}
+
+static void mdp5_wb_encoder_addr_setup(struct drm_encoder *encoder,
+	struct msm_wb_buffer *buf)
+{
+	struct mdp5_wb_encoder *mdp5_wb_encoder = to_mdp5_wb_encoder(encoder);
+	struct mdp5_kms *mdp5_kms = get_kms(encoder);
+	u32 wb_id = mdp5_wb_encoder->wb_id;
+
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST0_ADDR(wb_id), buf->iova[0]);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST1_ADDR(wb_id), buf->iova[1]);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST2_ADDR(wb_id), buf->iova[2]);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST3_ADDR(wb_id), buf->iova[3]);
+	DBG("Program WB DST address %x %x %x %x", buf->iova[0],
+		buf->iova[1], buf->iova[2], buf->iova[3]);
+	/* Notify ctl that wb buffer is ready to trigger start */
+	mdp5_ctl_commit(mdp5_wb_encoder->ctl,
+		mdp_ctl_flush_mask_encoder(&mdp5_wb_encoder->intf));
+}
+
+static void wb_csc_setup(struct mdp5_kms *mdp5_kms, u32 wb_id,
+		struct csc_cfg *csc)
+{
+	uint32_t  i;
+	uint32_t *matrix;
+
+	if (unlikely(!csc))
+		return;
+
+	matrix = csc->matrix;
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_0(wb_id),
+			MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11(matrix[0]) |
+			MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12(matrix[1]));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_1(wb_id),
+			MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13(matrix[2]) |
+			MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21(matrix[3]));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_2(wb_id),
+			MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22(matrix[4]) |
+			MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23(matrix[5]));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_3(wb_id),
+			MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31(matrix[6]) |
+			MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32(matrix[7]));
+	mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_MATRIX_COEFF_4(wb_id),
+			MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33(matrix[8]));
+
+	for (i = 0; i < ARRAY_SIZE(csc->pre_bias); i++) {
+		uint32_t *pre_clamp = csc->pre_clamp;
+		uint32_t *post_clamp = csc->post_clamp;
+
+		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PRECLAMP(wb_id, i),
+			MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH(pre_clamp[2*i+1]) |
+			MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW(pre_clamp[2*i]));
+
+		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTCLAMP(wb_id, i),
+			MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH(post_clamp[2*i+1]) |
+			MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW(post_clamp[2*i]));
+
+		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_PREBIAS(wb_id, i),
+			MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE(csc->pre_bias[i]));
+
+		mdp5_write(mdp5_kms, REG_MDP5_WB_CSC_COMP_POSTBIAS(wb_id, i),
+			MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE(csc->post_bias[i]));
+	}
+}
+
+static void mdp5_wb_encoder_mode_set(struct drm_encoder *encoder,
+		struct drm_display_mode *mode,
+		struct drm_display_mode *adjusted_mode)
+{
+	struct mdp5_wb_encoder *mdp5_wb_encoder = to_mdp5_wb_encoder(encoder);
+	struct mdp5_kms *mdp5_kms = get_kms(encoder);
+	struct msm_kms *kms = &mdp5_kms->base.base;
+	const struct msm_format *msm_fmt;
+	const struct mdp_format *fmt;
+	struct msm_wb *wb = get_wb(encoder);
+	struct msm_wb_buf_format *wb_buf_fmt;
+	struct msm_wb_buffer *buf;
+	u32 wb_id;
+	u32 dst_format, pattern, ystride0, ystride1, outsize, chroma_samp;
+	u32 opmode = 0;
+
+	DBG("Wb2 encoder modeset");
+
+	/* now we can get the ctl from crtc and extract the wb_id from ctl */
+	if (!mdp5_wb_encoder->ctl)
+		mdp5_wb_encoder->ctl = mdp5_crtc_get_ctl(encoder->crtc);
+
+	wb_id = mdp5_ctl_get_ctl_id(mdp5_wb_encoder->ctl);
+	mdp5_wb_encoder->wb_id = wb_id;
+
+	/* get color_format from wb device */
+	wb_buf_fmt = msm_wb_get_buf_format(wb);
+	msm_fmt = kms->funcs->get_format(kms, wb_buf_fmt->pixel_format);
+	if (!msm_fmt) {
+		pr_err("%s: Unsupported Color Format %d\n", __func__,
+			wb_buf_fmt->pixel_format);
+		return;
+	}
+
+	fmt = to_mdp_format(msm_fmt);
+	chroma_samp = fmt->chroma_sample;
+
+	if (MDP_FORMAT_IS_YUV(fmt)) {
+		/*  config csc */
+		DBG("YUV output %d, configure CSC",
+			fmt->base.pixel_format);
+		wb_csc_setup(mdp5_kms, mdp5_wb_encoder->wb_id,
+			mdp_get_default_csc_cfg(CSC_RGB2YUV));
+		opmode |= MDP5_WB_DST_OP_MODE_CSC_EN |
+			MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT(
+				DATA_FORMAT_RGB) |
+			MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT(
+				DATA_FORMAT_YUV);
+
+		switch (chroma_samp) {
+		case CHROMA_420:
+		case CHROMA_H2V1:
+			opmode |= MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_EN;
+			break;
+		case CHROMA_H1V2:
+		default:
+			pr_err("unsupported wb chroma samp=%d\n", chroma_samp);
+			return;
+		}
+	}
+
+	dst_format = MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP(chroma_samp) |
+		MDP5_WB_DST_FORMAT_WRITE_PLANES(fmt->fetch_type) |
+		MDP5_WB_DST_FORMAT_DSTC3_OUT(fmt->bpc_a) |
+		MDP5_WB_DST_FORMAT_DSTC2_OUT(fmt->bpc_r) |
+		MDP5_WB_DST_FORMAT_DSTC1_OUT(fmt->bpc_b) |
+		MDP5_WB_DST_FORMAT_DSTC0_OUT(fmt->bpc_g) |
+		COND(fmt->unpack_tight, MDP5_WB_DST_FORMAT_PACK_TIGHT) |
+		MDP5_WB_DST_FORMAT_PACK_COUNT(fmt->unpack_count - 1) |
+		MDP5_WB_DST_FORMAT_DST_BPP(fmt->cpp - 1);
+
+	if (fmt->bpc_a || fmt->alpha_enable) {
+		dst_format |= MDP5_WB_DST_FORMAT_DSTC3_EN;
+		if (!fmt->alpha_enable)
+			dst_format |= MDP5_WB_DST_FORMAT_DST_ALPHA_X;
+	}
+
+	pattern = MDP5_WB_DST_PACK_PATTERN_ELEMENT3(fmt->unpack[3]) |
+		MDP5_WB_DST_PACK_PATTERN_ELEMENT2(fmt->unpack[2]) |
+		MDP5_WB_DST_PACK_PATTERN_ELEMENT1(fmt->unpack[1]) |
+		MDP5_WB_DST_PACK_PATTERN_ELEMENT0(fmt->unpack[0]);
+
+	/* get the stride info from WB device */
+	ystride0 = MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE(wb_buf_fmt->pitches[0]) |
+		MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE(wb_buf_fmt->pitches[1]);
+	ystride1 = MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE(wb_buf_fmt->pitches[2]) |
+		MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE(wb_buf_fmt->pitches[3]);
+
+	/* get the output resolution from WB device */
+	outsize = MDP5_WB_OUT_SIZE_DST_H(wb_buf_fmt->height) |
+		MDP5_WB_OUT_SIZE_DST_W(wb_buf_fmt->width);
+
+	mdp5_write(mdp5_kms, REG_MDP5_WB_ALPHA_X_VALUE(wb_id), 0xFF);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_FORMAT(wb_id), dst_format);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_OP_MODE(wb_id), opmode);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_PACK_PATTERN(wb_id), pattern);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE0(wb_id), ystride0);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_DST_YSTRIDE1(wb_id), ystride1);
+	mdp5_write(mdp5_kms, REG_MDP5_WB_OUT_SIZE(wb_id), outsize);
+
+	mdp5_crtc_set_intf(encoder->crtc, &mdp5_wb_encoder->intf);
+
+	/* program the dst address */
+	buf = msm_wb_dequeue_buf(wb, MSM_WB_BUF_Q_FREE);
+	/*
+	 * if no free buffer is available, the only possibility is
+	 * WB connector becomes offline. User app should be notified
+	 * by udev event and stop the rendering soon.
+	 * so don't do anything here.
+	 */
+	if (!buf) {
+		pr_warn("%s: No buffer available\n", __func__);
+		return;
+	}
+
+	/* Last step of mode set: set up dst address */
+	msm_wb_queue_buf(wb, buf, MSM_WB_BUF_Q_ACTIVE);
+	mdp5_wb_encoder_addr_setup(encoder, buf);
+}
+
+static void mdp5_wb_encoder_disable(struct drm_encoder *encoder)
+{
+	struct mdp5_wb_encoder *mdp5_wb_encoder = to_mdp5_wb_encoder(encoder);
+	struct mdp5_kms *mdp5_kms = get_kms(encoder);
+	struct mdp5_ctl *ctl = mdp5_crtc_get_ctl(encoder->crtc);
+	struct msm_wb *wb = get_wb(encoder);
+	struct msm_wb_buffer *buf;
+
+	DBG("Disable wb encoder");
+
+	if (WARN_ON(!mdp5_wb_encoder->enabled))
+		return;
+
+	mdp5_ctl_set_encoder_state(ctl, false);
+
+	mdp_irq_unregister(&mdp5_kms->base,
+		&mdp5_wb_encoder->wb_vblank);
+
+	/* move the active buf to free buf queue*/
+	while ((buf = msm_wb_dequeue_buf(wb, MSM_WB_BUF_Q_ACTIVE))
+		!= NULL)
+		msm_wb_queue_buf(wb, buf, MSM_WB_BUF_Q_FREE);
+
+	msm_wb_update_encoder_state(wb, false);
+	bs_set(mdp5_wb_encoder, 0);
+
+	mdp5_wb_encoder->enabled = false;
+}
+
+static void mdp5_wb_encoder_enable(struct drm_encoder *encoder)
+{
+	struct mdp5_wb_encoder *mdp5_wb_encoder = to_mdp5_wb_encoder(encoder);
+	struct mdp5_kms *mdp5_kms = get_kms(encoder);
+	struct mdp5_ctl *ctl = mdp5_crtc_get_ctl(encoder->crtc);
+	struct msm_wb *wb = get_wb(encoder);
+
+	DBG("Enable wb encoder");
+
+	if (WARN_ON(mdp5_wb_encoder->enabled))
+		return;
+
+	bs_set(mdp5_wb_encoder, 1);
+	mdp_irq_register(&mdp5_kms->base,
+		&mdp5_wb_encoder->wb_vblank);
+
+
+	mdp5_ctl_set_encoder_state(ctl, true);
+	msm_wb_update_encoder_state(wb, true);
+
+	mdp5_wb_encoder->enabled = true;
+}
+
+static const struct drm_encoder_helper_funcs mdp5_wb_encoder_helper_funcs = {
+	.mode_fixup = mdp5_wb_encoder_mode_fixup,
+	.mode_set = mdp5_wb_encoder_mode_set,
+	.disable = mdp5_wb_encoder_disable,
+	.enable = mdp5_wb_encoder_enable,
+};
+
+static void mdp5_wb_encoder_vblank_irq(struct mdp_irq *irq, uint32_t irqstatus)
+{
+	struct mdp5_wb_encoder *mdp5_wb_encoder =
+		container_of(irq, struct mdp5_wb_encoder, wb_vblank);
+	struct mdp5_kms *mdp5_kms = get_kms(&mdp5_wb_encoder->base);
+	struct msm_wb *wb = get_wb(&mdp5_wb_encoder->base);
+	u32 wb_id = mdp5_wb_encoder->wb_id;
+	struct msm_wb_buffer *new_buf, *buf;
+	u32 reg_val;
+
+	DBG("wb id %d", wb_id);
+
+	reg_val = mdp5_read(mdp5_kms, REG_MDP5_WB_DST0_ADDR(wb_id));
+	buf = msm_wb_dequeue_buf(wb, MSM_WB_BUF_Q_ACTIVE);
+	if (WARN_ON(!buf || (reg_val != buf->iova[0]))) {
+		if (!buf)
+			pr_err("%s: no active buffer\n", __func__);
+		else
+			pr_err("%s: current addr %x expect %x\n",
+				__func__, reg_val, buf->iova[0]);
+		return;
+	}
+
+	/* retrieve the free buffer */
+	new_buf = msm_wb_dequeue_buf(wb, MSM_WB_BUF_Q_FREE);
+	if (!new_buf) {
+		pr_info("%s: No buffer is available\n", __func__);
+		/* reuse current active buffer */
+		new_buf = buf;
+	} else {
+		msm_wb_buf_captured(wb, buf, false);
+	}
+
+	/* Update the address anyway to trigger the WB flush */
+	msm_wb_queue_buf(wb, new_buf, MSM_WB_BUF_Q_ACTIVE);
+	mdp5_wb_encoder_addr_setup(&mdp5_wb_encoder->base, new_buf);
+}
+
+/* initialize encoder */
+struct drm_encoder *mdp5_wb_encoder_init(struct drm_device *dev,
+				struct mdp5_interface *intf)
+{
+	struct drm_encoder *encoder = NULL;
+	struct mdp5_wb_encoder *mdp5_wb_encoder;
+	int ret;
+
+	DBG("Init writeback encoder");
+
+	mdp5_wb_encoder = kzalloc(sizeof(*mdp5_wb_encoder), GFP_KERNEL);
+	if (!mdp5_wb_encoder) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	memcpy(&mdp5_wb_encoder->intf, intf, sizeof(mdp5_wb_encoder->intf));
+	encoder = &mdp5_wb_encoder->base;
+
+	drm_encoder_init(dev, encoder, &mdp5_wb_encoder_funcs,
+		 DRM_MODE_ENCODER_VIRTUAL);
+	drm_encoder_helper_add(encoder, &mdp5_wb_encoder_helper_funcs);
+
+	mdp5_wb_encoder->wb_vblank.irq = mdp5_wb_encoder_vblank_irq;
+	mdp5_wb_encoder->wb_vblank.irqmask = intf2vblank(0, intf);
+
+	bs_init(mdp5_wb_encoder);
+
+	return encoder;
+
+fail:
+	if (encoder)
+		mdp5_wb_encoder_destroy(encoder);
+
+	return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/msm/mdp/mdp_kms.h b/drivers/gpu/drm/msm/mdp/mdp_kms.h
index 5ae4039..2d3428c 100644
--- a/drivers/gpu/drm/msm/mdp/mdp_kms.h
+++ b/drivers/gpu/drm/msm/mdp/mdp_kms.h
@@ -88,7 +88,7 @@  struct mdp_format {
 	uint8_t unpack[4];
 	bool alpha_enable, unpack_tight;
 	uint8_t cpp, unpack_count;
-	enum mdp_sspp_fetch_type fetch_type;
+	enum mdp_fetch_type fetch_type;
 	enum mdp_chroma_samp_type chroma_sample;
 };
 #define to_mdp_format(x) container_of(x, struct mdp_format, base)
diff --git a/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.c b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.c
new file mode 100644
index 0000000..d9fc633
--- /dev/null
+++ b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.c
@@ -0,0 +1,311 @@ 
+/* Copyright (c) 2015, 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.
+ */
+
+#include "mdp_wb.h"
+#include "msm_kms.h"
+#include "../mdp_kms.h"
+
+struct msm_wb_priv_data {
+	bool streaming;
+
+	struct msm_wb_buf_format fmt;
+	/* buf queue */
+	struct msm_wb_buf_queue vidq;
+	spinlock_t vidq_lock;
+
+	/* wait queue to sync between v4l2 and drm during stream off */
+	bool encoder_on;
+	wait_queue_head_t encoder_state_wq;
+};
+
+void msm_wb_update_encoder_state(struct msm_wb *wb, bool enable)
+{
+	wb->priv_data->encoder_on = enable;
+	wake_up_all(&wb->priv_data->encoder_state_wq);
+}
+
+struct msm_wb_buf_format *msm_wb_get_buf_format(struct msm_wb *wb)
+{
+	return &wb->priv_data->fmt;
+}
+
+int msm_wb_set_buf_format(struct msm_wb *wb, u32 pixel_fmt,
+		u32 width, u32 height)
+{
+	struct msm_drm_private *priv = wb->dev->dev_private;
+	struct msm_kms *kms = priv->kms;
+	const struct msm_format *msm_fmt;
+	const struct mdp_format *mdp_fmt;
+	struct msm_wb_buf_format *fmt = &wb->priv_data->fmt;
+
+	msm_fmt = kms->funcs->get_format(kms, pixel_fmt);
+	if (!msm_fmt) {
+		pr_err("%s: Unsupported Color Format %d\n", __func__,
+			pixel_fmt);
+		return -EINVAL;
+	}
+
+	mdp_fmt = to_mdp_format(msm_fmt);
+
+	fmt->pixel_format = pixel_fmt;
+	fmt->width = width;
+	fmt->height = height;
+	DBG("Set format %x width %d height %d", pixel_fmt, width, height);
+
+	switch (mdp_fmt->fetch_type) {
+	case MDP_PLANE_INTERLEAVED:
+		fmt->plane_num = 1;
+		fmt->pitches[0] = width * mdp_fmt->cpp;
+		break;
+	case MDP_PLANE_PLANAR:
+		fmt->plane_num = 3;
+		fmt->pitches[0] = width;
+		fmt->pitches[1] = width;
+		fmt->pitches[2] = width;
+		if (mdp_fmt->alpha_enable) {
+			fmt->plane_num = 4;
+			fmt->pitches[3] = width;
+		}
+		break;
+	case MDP_PLANE_PSEUDO_PLANAR:
+		fmt->plane_num = 2;
+		fmt->pitches[0] = width;
+		switch (mdp_fmt->chroma_sample) {
+		case CHROMA_H2V1:
+		case CHROMA_420:
+			fmt->pitches[1] = width/2;
+			break;
+		case CHROMA_H1V2:
+			fmt->pitches[1] = width;
+			break;
+		default:
+			pr_err("%s: Not supported fmt\n", __func__);
+			return -EINVAL;
+		}
+		break;
+	}
+
+	return 0;
+}
+
+void msm_wb_queue_buf(struct msm_wb *wb, struct msm_wb_buffer *wb_buf,
+	enum msm_wb_buf_queue_type type)
+{
+	unsigned long flags;
+	struct list_head *q;
+
+	if (type == MSM_WB_BUF_Q_FREE)
+		q = &wb->priv_data->vidq.free;
+	else
+		q = &wb->priv_data->vidq.active;
+
+	if (type == MSM_WB_BUF_Q_FREE)
+		mdp5_wb_encoder_buf_prepare(wb, wb_buf);
+
+	spin_lock_irqsave(&wb->priv_data->vidq_lock, flags);
+	list_add_tail(&wb_buf->list, q);
+	spin_unlock_irqrestore(&wb->priv_data->vidq_lock, flags);
+}
+
+struct msm_wb_buffer *msm_wb_dequeue_buf(struct msm_wb *wb,
+	enum msm_wb_buf_queue_type type)
+{
+	struct msm_wb_buffer *buf = NULL;
+	unsigned long flags;
+	struct list_head *q;
+
+	if (type == MSM_WB_BUF_Q_FREE)
+		q = &wb->priv_data->vidq.free;
+	else
+		q = &wb->priv_data->vidq.active;
+
+	spin_lock_irqsave(&wb->priv_data->vidq_lock, flags);
+	if (!list_empty(q)) {
+		buf = list_entry(q->next,
+				struct msm_wb_buffer, list);
+		list_del(&buf->list);
+	}
+	spin_unlock_irqrestore(&wb->priv_data->vidq_lock, flags);
+
+	return buf;
+}
+
+int msm_wb_start_streaming(struct msm_wb *wb)
+{
+	if (wb->priv_data->streaming) {
+		pr_err("%s: wb is streaming\n", __func__);
+		return -EBUSY;
+	}
+
+	DBG("Stream ON");
+	wb->priv_data->streaming = true;
+	msm_wb_connector_hotplug(wb, wb->priv_data->streaming);
+
+	return 0;
+}
+
+int msm_wb_stop_streaming(struct msm_wb *wb)
+{
+	int rc;
+	struct msm_wb_buffer *buf;
+
+	if (!wb->priv_data->streaming) {
+		pr_info("%s: wb is not streaming\n", __func__);
+		return -EINVAL;
+	}
+
+	DBG("Stream off");
+	wb->priv_data->streaming = false;
+	msm_wb_connector_hotplug(wb, wb->priv_data->streaming);
+
+	/* wait until drm encoder off */
+	rc = wait_event_timeout(wb->priv_data->encoder_state_wq,
+		!wb->priv_data->encoder_on, 10 * HZ);
+	if (!rc) {
+		pr_err("%s: wait encoder off timeout\n", __func__);
+		return -ETIMEDOUT;
+	}
+
+	/* flush all active and free buffers */
+	while ((buf = msm_wb_dequeue_buf(wb, MSM_WB_BUF_Q_ACTIVE)) != NULL)
+		msm_wb_buf_captured(wb, buf, true);
+
+	while ((buf = msm_wb_dequeue_buf(wb, MSM_WB_BUF_Q_FREE)) != NULL)
+		msm_wb_buf_captured(wb, buf, true);
+
+	DBG("Stream turned off");
+
+	return 0;
+}
+
+int msm_wb_modeset_init(struct msm_wb *wb,
+	struct drm_device *dev, struct drm_encoder *encoder)
+{
+	struct msm_drm_private *priv = dev->dev_private;
+	int ret;
+
+	wb->dev = dev;
+	wb->encoder = encoder;
+
+	wb->connector = msm_wb_connector_init(wb);
+	if (IS_ERR(wb->connector)) {
+		ret = PTR_ERR(wb->connector);
+		dev_err(dev->dev, "failed to create WB connector: %d\n", ret);
+		wb->connector = NULL;
+		return ret;
+	}
+
+	priv->connectors[priv->num_connectors++] = wb->connector;
+
+	return 0;
+}
+
+static void msm_wb_destroy(struct msm_wb *wb)
+{
+	platform_set_drvdata(wb->pdev, NULL);
+}
+
+static struct msm_wb *msm_wb_init(struct platform_device *pdev)
+{
+	struct msm_wb *wb = NULL;
+
+	wb = devm_kzalloc(&pdev->dev, sizeof(*wb), GFP_KERNEL);
+	if (!wb)
+		return ERR_PTR(-ENOMEM);
+
+	wb->pdev = pdev;
+	wb->priv_data = devm_kzalloc(&pdev->dev, sizeof(*wb->priv_data),
+		GFP_KERNEL);
+	if (!wb->priv_data)
+		return ERR_PTR(-ENOMEM);
+
+	if (msm_wb_v4l2_init(wb)) {
+		pr_err("%s: wb v4l2 init failed\n", __func__);
+		return ERR_PTR(-ENODEV);
+	}
+
+	spin_lock_init(&wb->priv_data->vidq_lock);
+	INIT_LIST_HEAD(&wb->priv_data->vidq.active);
+	INIT_LIST_HEAD(&wb->priv_data->vidq.free);
+	init_waitqueue_head(&wb->priv_data->encoder_state_wq);
+
+	platform_set_drvdata(pdev, wb);
+
+	return wb;
+}
+
+static int msm_wb_bind(struct device *dev, struct device *master, void *data)
+{
+	struct drm_device *drm = dev_get_drvdata(master);
+	struct msm_drm_private *priv = drm->dev_private;
+	struct msm_wb *wb;
+
+	wb = msm_wb_init(to_platform_device(dev));
+	if (IS_ERR(wb))
+		return PTR_ERR(wb);
+
+	priv->wb = wb;
+
+	return 0;
+}
+
+static void msm_wb_unbind(struct device *dev, struct device *master,
+		void *data)
+{
+	struct drm_device *drm = dev_get_drvdata(master);
+	struct msm_drm_private *priv = drm->dev_private;
+
+	if (priv->wb) {
+		msm_wb_destroy(priv->wb);
+		priv->wb = NULL;
+	}
+}
+
+static const struct component_ops msm_wb_ops = {
+		.bind   = msm_wb_bind,
+		.unbind = msm_wb_unbind,
+};
+
+static int msm_wb_dev_probe(struct platform_device *pdev)
+{
+	return component_add(&pdev->dev, &msm_wb_ops);
+}
+
+static int msm_wb_dev_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &msm_wb_ops);
+	return 0;
+}
+
+static const struct of_device_id dt_match[] = {
+	{ .compatible = "qcom,mdss_wb"},
+	{}
+};
+
+static struct platform_driver msm_wb_driver = {
+	.probe = msm_wb_dev_probe,
+	.remove = msm_wb_dev_remove,
+	.driver = {
+		.name = "wb_msm",
+		.of_match_table = dt_match,
+	},
+};
+
+void __init msm_wb_register(void)
+{
+	platform_driver_register(&msm_wb_driver);
+}
+
+void __exit msm_wb_unregister(void)
+{
+	platform_driver_unregister(&msm_wb_driver);
+}
diff --git a/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.h b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.h
new file mode 100644
index 0000000..a970b00
--- /dev/null
+++ b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.h
@@ -0,0 +1,98 @@ 
+/* Copyright (c) 2015, 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 __MDP_WB_H__
+#define __MDP_WB_H__
+
+#include <linux/platform_device.h>
+#include "msm_kms.h"
+
+struct vb2_buffer;
+
+struct msm_wb_buffer {
+	struct list_head list;
+	struct drm_gem_object *planes[MAX_PLANE];
+	u32 pixel_format;
+	u32 offsets[MAX_PLANE];
+	u32 iova[MAX_PLANE];
+	struct vb2_buffer *vb; /* v4l2 buffer */
+};
+
+struct msm_wb_buf_format {
+	u32 pixel_format;
+	u32 width;
+	u32 height;
+	u32 plane_num;
+	u32 pitches[MAX_PLANE];
+};
+
+enum msm_wb_buf_queue_type {
+	MSM_WB_BUF_Q_FREE = 0,
+	MSM_WB_BUF_Q_ACTIVE,
+	MSM_WB_BUF_Q_NUM
+};
+
+struct msm_wb_buf_queue {
+	struct list_head free;
+	struct list_head active;
+};
+
+struct msm_wb_priv_data;
+struct msm_wb {
+	struct drm_device *dev;
+	struct platform_device *pdev;
+
+	struct drm_connector *connector;
+	struct drm_encoder *encoder;
+
+	void *wb_v4l2;
+
+	struct msm_wb_priv_data *priv_data;
+};
+
+int msm_wb_start_streaming(struct msm_wb *wb);
+int msm_wb_stop_streaming(struct msm_wb *wb);
+void mdp5_wb_encoder_buf_prepare(struct msm_wb *wb, struct msm_wb_buffer *buf);
+void msm_wb_connector_hotplug(struct msm_wb *wb, bool connected);
+int msm_wb_set_buf_format(struct msm_wb *wb, u32 pixel_fmt,
+	u32 width, u32 height);
+
+#ifdef CONFIG_DRM_MSM_WB
+struct msm_wb_buf_format *msm_wb_get_buf_format(struct msm_wb *wb);
+void msm_wb_queue_buf(struct msm_wb *wb, struct msm_wb_buffer *buf,
+	enum msm_wb_buf_queue_type type);
+struct msm_wb_buffer *msm_wb_dequeue_buf(struct msm_wb *wb,
+	enum msm_wb_buf_queue_type type);
+void msm_wb_update_encoder_state(struct msm_wb *wb, bool enable);
+void msm_wb_buf_captured(struct msm_wb *wb, struct msm_wb_buffer *buf,
+	bool discard);
+#else
+static inline struct msm_wb_buf_format *msm_wb_get_buf_format(
+	struct msm_wb *wb) { return NULL; }
+static inline void msm_wb_queue_buf(struct msm_wb *wb,
+	struct msm_wb_buffer *buf, enum msm_wb_buf_queue_type type) {}
+static inline struct msm_wb_buffer *msm_wb_dequeue_buf(struct msm_wb *wb,
+	enum msm_wb_buf_queue_type type) { return NULL; }
+static inline void msm_wb_update_encoder_state(struct msm_wb *wb,
+	bool enable) {}
+static inline void msm_wb_buf_captured(struct msm_wb *wb,
+	struct msm_wb_buffer *buf, bool discard) {}
+#endif
+
+int msm_wb_v4l2_init(struct msm_wb *wb);
+
+/*
+ * wb connector:
+ */
+struct drm_connector *msm_wb_connector_init(struct msm_wb *wb);
+
+#endif /* __MDP_WB_H__ */
diff --git a/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_connector.c b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_connector.c
new file mode 100644
index 0000000..814dec9
--- /dev/null
+++ b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_connector.c
@@ -0,0 +1,157 @@ 
+/* Copyright (c) 2015, 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.
+ */
+
+#include "mdp_wb.h"
+
+struct msm_wb_connector {
+	struct drm_connector base;
+	struct msm_wb *wb;
+	struct work_struct hpd_work;
+	bool connected;
+};
+#define to_wb_connector(x) container_of(x, struct msm_wb_connector, base)
+
+static enum drm_connector_status msm_wb_connector_detect(
+		struct drm_connector *connector, bool force)
+{
+	struct msm_wb_connector *wb_connector = to_wb_connector(connector);
+
+	DBG("%s", wb_connector->connected ? "connected" : "disconnected");
+	return wb_connector->connected ?
+		connector_status_connected : connector_status_disconnected;
+}
+
+static void msm_wb_hotplug_work(struct work_struct *work)
+{
+	struct msm_wb_connector *wb_connector =
+		container_of(work, struct msm_wb_connector, hpd_work);
+	struct drm_connector *connector = &wb_connector->base;
+
+	drm_kms_helper_hotplug_event(connector->dev);
+}
+
+void msm_wb_connector_hotplug(struct msm_wb *wb, bool connected)
+{
+	struct drm_connector *connector = wb->connector;
+	struct msm_wb_connector *wb_connector = to_wb_connector(connector);
+	struct msm_drm_private *priv = connector->dev->dev_private;
+
+	wb_connector->connected = connected;
+	queue_work(priv->wq, &wb_connector->hpd_work);
+}
+
+static void msm_wb_connector_destroy(struct drm_connector *connector)
+{
+	struct msm_wb_connector *wb_connector = to_wb_connector(connector);
+
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+
+	kfree(wb_connector);
+}
+
+static int msm_wb_connector_get_modes(struct drm_connector *connector)
+{
+	struct msm_wb_connector *wb_connector = to_wb_connector(connector);
+	struct msm_wb *wb = wb_connector->wb;
+	struct msm_wb_buf_format *wb_buf_fmt;
+	struct drm_display_mode *mode = NULL;
+
+	wb_buf_fmt = msm_wb_get_buf_format(wb);
+	mode = drm_cvt_mode(connector->dev, wb_buf_fmt->width,
+		wb_buf_fmt->height, 60, false, false, false);
+
+	if (!mode) {
+		pr_err("%s: failed to create mode\n", __func__);
+		return -ENOTSUPP;
+	}
+
+	drm_mode_probed_add(connector, mode);
+
+	return 1;
+}
+
+static int msm_wb_connector_mode_valid(struct drm_connector *connector,
+				 struct drm_display_mode *mode)
+{
+	return 0;
+}
+
+static struct drm_encoder *
+msm_wb_connector_best_encoder(struct drm_connector *connector)
+{
+	struct msm_wb_connector *wb_connector = to_wb_connector(connector);
+
+	return wb_connector->wb->encoder;
+}
+
+static const struct drm_connector_funcs msm_wb_connector_funcs = {
+	.dpms = drm_helper_connector_dpms,
+	.detect = msm_wb_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = msm_wb_connector_destroy,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+
+};
+
+static const struct drm_connector_helper_funcs msm_wb_connector_helper_funcs = {
+	.get_modes = msm_wb_connector_get_modes,
+	.mode_valid = msm_wb_connector_mode_valid,
+	.best_encoder = msm_wb_connector_best_encoder,
+};
+
+/* initialize connector */
+struct drm_connector *msm_wb_connector_init(struct msm_wb *wb)
+{
+	struct drm_connector *connector = NULL;
+	struct msm_wb_connector *wb_connector;
+	int ret;
+
+	wb_connector = kzalloc(sizeof(*wb_connector), GFP_KERNEL);
+	if (!wb_connector) {
+		ret = -ENOMEM;
+		goto fail;
+	}
+
+	wb_connector->wb = wb;
+	connector = &wb_connector->base;
+
+	ret = drm_connector_init(wb->dev, connector, &msm_wb_connector_funcs,
+			DRM_MODE_CONNECTOR_VIRTUAL);
+	if (ret)
+		goto fail;
+
+	drm_connector_helper_add(connector, &msm_wb_connector_helper_funcs);
+
+	connector->polled = DRM_CONNECTOR_POLL_HPD;
+
+	connector->interlace_allowed = 0;
+	connector->doublescan_allowed = 0;
+
+	drm_connector_register(connector);
+
+	ret = drm_mode_connector_attach_encoder(connector, wb->encoder);
+	if (ret)
+		goto fail;
+
+	INIT_WORK(&wb_connector->hpd_work, msm_wb_hotplug_work);
+
+	return connector;
+
+fail:
+	if (connector)
+		msm_wb_connector_destroy(connector);
+
+	return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_v4l2.c b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_v4l2.c
new file mode 100644
index 0000000..3822f6c
--- /dev/null
+++ b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_v4l2.c
@@ -0,0 +1,501 @@ 
+/* Copyright (c) 2015, 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.
+ */
+
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-common.h>
+#include <media/videobuf2-core.h>
+
+#include "mdp_wb.h"
+
+#define MAX_WIDTH 2048
+#define MAX_HEIGHT 2048
+
+struct msm_wb_fmt {
+	const char *name;
+	u32 fourcc;          /* v4l2 format id */
+	u32 drm_fourcc;      /* drm format id */
+	u8 depth;
+	u8 plane_cnt;
+	u32 plane_bpp[MAX_PLANE]; /* bit per pixel per plalne */
+	bool  is_yuv;
+};
+
+static const struct msm_wb_fmt formats[] = {
+	{
+		.name     = "Y/CbCr 4:2:0",
+		.fourcc   = V4L2_PIX_FMT_NV12,
+		.drm_fourcc = DRM_FORMAT_NV12,
+		.depth    = 12,
+		.plane_cnt = 2,
+		.plane_bpp = {8, 4, 0, 0},
+		.is_yuv   = true,
+	},
+	{
+		.name     = "Y/CrCb 4:2:0",
+		.fourcc   = V4L2_PIX_FMT_NV21,
+		.drm_fourcc = DRM_FORMAT_NV21,
+		.depth    = 12,
+		.plane_cnt = 2,
+		.plane_bpp = {8, 4, 0, 0},
+		.is_yuv   = true,
+	},
+	{
+		.name     = "RGB24",
+		.fourcc   = V4L2_PIX_FMT_RGB24,
+		.drm_fourcc = DRM_FORMAT_RGB888,
+		.depth    = 24,
+		.plane_cnt = 2,
+		.plane_bpp = {24, 0, 0, 0},
+	},
+	{
+		.name     = "ARGB32",
+		.fourcc   = V4L2_PIX_FMT_RGB32,
+		.drm_fourcc = DRM_FORMAT_ARGB8888,
+		.depth    = 32,
+		.plane_cnt = 1,
+		.plane_bpp = {24, 0, 0, 0},
+	},
+};
+
+/* buffer for one video frame */
+struct msm_wb_v4l2_buffer {
+	/* common v4l buffer stuff -- must be first */
+	struct vb2_buffer vb;
+	struct msm_wb_buffer wb_buf;
+};
+
+struct msm_wb_v4l2_dev {
+	struct v4l2_device v4l2_dev;
+	struct video_device vdev;
+
+	struct mutex mutex;
+
+	/* video capture */
+	const struct msm_wb_fmt *fmt;
+	unsigned int width, height;
+
+	struct vb2_queue vb_vidq;
+
+	struct msm_wb *wb;
+};
+
+static const struct msm_wb_fmt *get_format(u32 fourcc)
+{
+	const struct msm_wb_fmt *fmt;
+	unsigned int k;
+
+	for (k = 0; k < ARRAY_SIZE(formats); k++) {
+		fmt = &formats[k];
+		if (fmt->fourcc == fourcc)
+			return fmt;
+	}
+
+	return NULL;
+}
+
+void msm_wb_buf_captured(struct msm_wb *wb,
+	struct msm_wb_buffer *buf, bool discard)
+{
+	struct msm_wb_v4l2_buffer *v4l2_buf =
+		container_of(buf, struct msm_wb_v4l2_buffer, wb_buf);
+	enum vb2_buffer_state buf_state = discard ? VB2_BUF_STATE_ERROR :
+			VB2_BUF_STATE_DONE;
+
+	v4l2_get_timestamp(&v4l2_buf->vb.v4l2_buf.timestamp);
+	vb2_buffer_done(&v4l2_buf->vb, buf_state);
+}
+
+/* ------------------------------------------------------------------
+	DMA buffer operations
+   ------------------------------------------------------------------*/
+
+static int msm_wb_vb2_map_dmabuf(void *mem_priv)
+{
+	return 0;
+}
+
+static void msm_wb_vb2_unmap_dmabuf(void *mem_priv)
+{
+}
+
+static void *msm_wb_vb2_attach_dmabuf(void *alloc_ctx, struct dma_buf *dbuf,
+	unsigned long size, int write)
+{
+	struct msm_wb_v4l2_dev *dev = alloc_ctx;
+	struct drm_device *drm_dev = dev->wb->dev;
+	struct drm_gem_object *obj;
+
+	obj = drm_dev->driver->gem_prime_import(drm_dev, dbuf);
+	if (IS_ERR(obj)) {
+		v4l2_err(&dev->v4l2_dev, "Can't convert dmabuf to gem obj.\n");
+		goto out;
+	}
+
+	if (obj->dma_buf) {
+		if (WARN_ON(obj->dma_buf != dbuf)) {
+			v4l2_err(&dev->v4l2_dev,
+				"dma buf doesn't match.\n");
+			obj = ERR_PTR(-EINVAL);
+		}
+	} else {
+		obj->dma_buf = dbuf;
+	}
+
+out:
+	return obj;
+}
+
+static void msm_wb_vb2_detach_dmabuf(void *mem_priv)
+{
+	struct drm_gem_object *obj = mem_priv;
+
+	drm_gem_object_unreference_unlocked(obj);
+}
+
+void *msm_wb_vb2_cookie(void *buf_priv)
+{
+	return buf_priv;
+}
+
+const struct vb2_mem_ops msm_wb_vb2_mem_ops = {
+	.map_dmabuf = msm_wb_vb2_map_dmabuf,
+	.unmap_dmabuf = msm_wb_vb2_unmap_dmabuf,
+	.attach_dmabuf = msm_wb_vb2_attach_dmabuf,
+	.detach_dmabuf = msm_wb_vb2_detach_dmabuf,
+	.cookie = msm_wb_vb2_cookie,
+};
+
+/* ------------------------------------------------------------------
+	Videobuf operations
+   ------------------------------------------------------------------*/
+#define MSM_WB_BUF_NUM_MIN 4
+
+static int msm_wb_vb2_queue_setup(struct vb2_queue *vq,
+		const struct v4l2_format *fmt,
+		unsigned int *nbuffers, unsigned int *nplanes,
+		unsigned int sizes[], void *alloc_ctxs[])
+{
+	struct msm_wb_v4l2_dev *dev = vb2_get_drv_priv(vq);
+	const struct msm_wb_fmt *wb_fmt = dev->fmt;
+	int i;
+
+	*nbuffers = MSM_WB_BUF_NUM_MIN;
+	*nplanes = wb_fmt->plane_cnt;
+
+	for (i = 0; i < *nplanes; i++) {
+		sizes[i] = (wb_fmt->plane_bpp[i] * dev->width *
+			dev->height) >> 3;
+		alloc_ctxs[i] = dev;
+	}
+
+	v4l2_info(dev, "%s, count=%d, plane count=%d\n", __func__,
+		*nbuffers, *nplanes);
+
+	return 0;
+}
+
+static int msm_wb_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+	return 0;
+}
+
+static void msm_wb_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct msm_wb_v4l2_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
+	struct msm_wb_v4l2_buffer *buf =
+		container_of(vb, struct msm_wb_v4l2_buffer, vb);
+	struct msm_wb_buffer *wb_buf = &buf->wb_buf;
+	int i;
+
+	/* pass the buffer to wb */
+	wb_buf->vb = vb;
+	wb_buf->pixel_format = dev->fmt->drm_fourcc;
+	for (i = 0; i < vb->num_planes; i++) {
+		wb_buf->offsets[i] = vb->v4l2_planes[i].data_offset;
+		wb_buf->planes[i] = vb2_plane_cookie(vb, i);
+		WARN_ON(!wb_buf->planes[i]);
+	}
+
+	msm_wb_queue_buf(dev->wb, wb_buf, MSM_WB_BUF_Q_FREE);
+}
+
+static int msm_wb_vb2_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct msm_wb_v4l2_dev *dev = vb2_get_drv_priv(vq);
+
+	v4l2_info(dev, "%s\n", __func__);
+
+	return msm_wb_start_streaming(dev->wb);
+}
+
+/* abort streaming and wait for last buffer */
+static int msm_wb_vb2_stop_streaming(struct vb2_queue *vq)
+{
+	struct msm_wb_v4l2_dev *dev = vb2_get_drv_priv(vq);
+
+	v4l2_info(dev, "%s\n", __func__);
+
+	return msm_wb_stop_streaming(dev->wb);
+}
+
+static const struct vb2_ops msm_wb_vb2_ops = {
+	.queue_setup = msm_wb_vb2_queue_setup,
+	.buf_prepare = msm_wb_vb2_buf_prepare,
+	.buf_queue = msm_wb_vb2_buf_queue,
+	.start_streaming = msm_wb_vb2_start_streaming,
+	.stop_streaming = msm_wb_vb2_stop_streaming,
+};
+
+/* ------------------------------------------------------------------
+	IOCTL vidioc handling
+   ------------------------------------------------------------------*/
+static int msm_wb_vidioc_querycap(struct file *file, void  *priv,
+					struct v4l2_capability *cap)
+{
+	struct msm_wb_v4l2_dev *dev = video_drvdata(file);
+
+	strcpy(cap->driver, "msm_wb");
+	strcpy(cap->card, "msm_wb");
+	snprintf(cap->bus_info, sizeof(cap->bus_info),
+			"platform:%s", dev->v4l2_dev.name);
+	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+	return 0;
+}
+
+static int msm_wb_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
+					struct v4l2_fmtdesc *f)
+{
+	struct msm_wb_v4l2_dev *dev = video_drvdata(file);
+	const struct msm_wb_fmt *fmt;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+		v4l2_err(&dev->v4l2_dev, "Invalid buf type %d.\n",
+			f->type);
+		return -EINVAL;
+	}
+
+	if (f->index >= ARRAY_SIZE(formats))
+		return -ERANGE;
+
+	fmt = &formats[f->index];
+
+	strlcpy(f->description, fmt->name, sizeof(f->description));
+	f->pixelformat = fmt->fourcc;
+
+	return 0;
+}
+
+static int msm_wb_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+					struct v4l2_format *f)
+{
+	struct msm_wb_v4l2_dev *dev = video_drvdata(file);
+	int i;
+
+	f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+	f->fmt.pix_mp.width        = dev->width;
+	f->fmt.pix_mp.height       = dev->height;
+	f->fmt.pix_mp.field        = V4L2_FIELD_NONE;
+	f->fmt.pix_mp.pixelformat  = dev->fmt->fourcc;
+	f->fmt.pix_mp.num_planes = dev->fmt->plane_cnt;
+
+	for (i = 0; i < dev->fmt->plane_cnt; i++) {
+		f->fmt.pix_mp.plane_fmt[i].bytesperline =
+			(dev->fmt->plane_bpp[i] * dev->width) >> 3;
+		f->fmt.pix_mp.plane_fmt[i].sizeimage =
+			f->fmt.pix_mp.plane_fmt[i].bytesperline * dev->height;
+	}
+
+	if (dev->fmt->is_yuv)
+		f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	else
+		f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SRGB;
+
+	return 0;
+}
+
+static int msm_wb_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+			struct v4l2_format *f)
+{
+	struct msm_wb_v4l2_dev *dev = video_drvdata(file);
+	const struct msm_wb_fmt *fmt;
+	int i;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+		v4l2_err(&dev->v4l2_dev, "Invalid buf type %d.\n",
+			f->type);
+		return -EINVAL;
+	}
+
+	fmt = get_format(f->fmt.pix_mp.pixelformat);
+	if (!fmt) {
+		v4l2_err(&dev->v4l2_dev, "Fourcc format (0x%08x) unknown.\n",
+			f->fmt.pix_mp.pixelformat);
+		return -ENOTSUPP;
+	}
+
+	f->fmt.pix_mp.field = V4L2_FIELD_NONE;
+	v4l_bound_align_image(&f->fmt.pix_mp.width, 48, MAX_WIDTH, 4,
+			      &f->fmt.pix_mp.height, 32, MAX_HEIGHT, 4, 0);
+	f->fmt.pix_mp.num_planes = fmt->plane_cnt;
+
+	for (i = 0; i < dev->fmt->plane_cnt; i++) {
+		f->fmt.pix_mp.plane_fmt[i].bytesperline =
+			(dev->fmt->plane_bpp[i] * f->fmt.pix_mp.width) >> 3;
+		f->fmt.pix_mp.plane_fmt[i].sizeimage =
+			f->fmt.pix_mp.plane_fmt[i].bytesperline *
+			f->fmt.pix_mp.height;
+	}
+
+	if (fmt->is_yuv)
+		f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	else
+		f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SRGB;
+
+	return 0;
+}
+
+static int msm_wb_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+					struct v4l2_format *f)
+{
+	struct msm_wb_v4l2_dev *dev = video_drvdata(file);
+	struct msm_wb *wb = dev->wb;
+	struct vb2_queue *q = &dev->vb_vidq;
+	int rc;
+
+	rc = msm_wb_vidioc_try_fmt_vid_cap(file, priv, f);
+	if (rc < 0)
+		return rc;
+
+	if (vb2_is_busy(q)) {
+		v4l2_err(&dev->v4l2_dev, "%s device busy\n", __func__);
+		return -EBUSY;
+	}
+
+	dev->fmt = get_format(f->fmt.pix_mp.pixelformat);
+	dev->width = f->fmt.pix_mp.width;
+	dev->height = f->fmt.pix_mp.height;
+
+	rc = msm_wb_set_buf_format(wb, dev->fmt->drm_fourcc,
+		dev->width, dev->height);
+	if (rc)
+		v4l2_err(&dev->v4l2_dev,
+			"Set format (0x%08x w:%x h:%x) failed.\n",
+			dev->fmt->drm_fourcc, dev->width, dev->height);
+
+	return rc;
+}
+
+static const struct v4l2_file_operations msm_wb_v4l2_fops = {
+	.owner = THIS_MODULE,
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.poll = vb2_fop_poll,
+	.unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops msm_wb_v4l2_ioctl_ops = {
+	.vidioc_querycap      = msm_wb_vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap_mplane = msm_wb_vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap_mplane = msm_wb_vidioc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap_mplane = msm_wb_vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap_mplane = msm_wb_vidioc_s_fmt_vid_cap,
+	.vidioc_reqbufs       = vb2_ioctl_reqbufs,
+	.vidioc_querybuf      = vb2_ioctl_querybuf,
+	.vidioc_qbuf          = vb2_ioctl_qbuf,
+	.vidioc_dqbuf         = vb2_ioctl_dqbuf,
+	.vidioc_streamon      = vb2_ioctl_streamon,
+	.vidioc_streamoff     = vb2_ioctl_streamoff,
+	.vidioc_log_status    = v4l2_ctrl_log_status,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct video_device msm_wb_v4l2_template = {
+	.name = "msm_wb",
+	.fops = &msm_wb_v4l2_fops,
+	.ioctl_ops = &msm_wb_v4l2_ioctl_ops,
+	.release = video_device_release_empty,
+};
+
+int msm_wb_v4l2_init(struct msm_wb *wb)
+{
+	struct msm_wb_v4l2_dev *dev;
+	struct video_device *vfd;
+	struct vb2_queue *q;
+	int ret;
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	strncpy(dev->v4l2_dev.name, "msm_wb", sizeof(dev->v4l2_dev.name));
+	ret = v4l2_device_register(NULL, &dev->v4l2_dev);
+	if (ret)
+		goto free_dev;
+
+	/* default ARGB8888 640x480 */
+	dev->fmt = get_format(V4L2_PIX_FMT_RGB32);
+	dev->width = 640;
+	dev->height = 480;
+
+	/* initialize queue */
+	q = &dev->vb_vidq;
+	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+	q->io_modes = VB2_DMABUF;
+	q->drv_priv = dev;
+	q->buf_struct_size = sizeof(struct msm_wb_v4l2_buffer);
+	q->ops = &msm_wb_vb2_ops;
+	q->mem_ops = &msm_wb_vb2_mem_ops;
+	q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+
+	ret = vb2_queue_init(q);
+	if (ret)
+		goto unreg_dev;
+
+	mutex_init(&dev->mutex);
+
+	vfd = &dev->vdev;
+	*vfd = msm_wb_v4l2_template;
+	vfd->v4l2_dev = &dev->v4l2_dev;
+	vfd->queue = q;
+
+	/*
+	 * Provide a mutex to v4l2 core. It will be used to protect
+	 * all fops and v4l2 ioctls.
+	 */
+	vfd->lock = &dev->mutex;
+	video_set_drvdata(vfd, dev);
+
+	ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1);
+	if (ret < 0)
+		goto unreg_dev;
+
+	dev->wb = wb;
+	wb->wb_v4l2 = dev;
+	v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",
+		  video_device_node_name(vfd));
+
+	return 0;
+
+unreg_dev:
+	v4l2_device_unregister(&dev->v4l2_dev);
+free_dev:
+	kfree(dev);
+	return ret;
+}
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index 47f4dd4..637c75d 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -1076,6 +1076,7 @@  static struct platform_driver msm_platform_driver = {
 static int __init msm_drm_register(void)
 {
 	DBG("init");
+	msm_wb_register();
 	msm_dsi_register();
 	msm_edp_register();
 	hdmi_register();
@@ -1091,6 +1092,7 @@  static void __exit msm_drm_unregister(void)
 	adreno_unregister();
 	msm_edp_unregister();
 	msm_dsi_unregister();
+	msm_wb_unregister();
 }
 
 module_init(msm_drm_register);
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index 04db4bd..423b666 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -85,6 +85,8 @@  struct msm_drm_private {
 	/* DSI is shared by mdp4 and mdp5 */
 	struct msm_dsi *dsi[2];
 
+	struct msm_wb *wb;
+
 	/* when we have more than one 'msm_gpu' these need to be an array: */
 	struct msm_gpu *gpu;
 	struct msm_file_private *lastctx;
@@ -265,6 +267,19 @@  static inline int msm_dsi_modeset_init(struct msm_dsi *msm_dsi,
 }
 #endif
 
+struct msm_wb;
+#ifdef CONFIG_DRM_MSM_WB
+void __init msm_wb_register(void);
+void __exit msm_wb_unregister(void);
+int msm_wb_modeset_init(struct msm_wb *wb, struct drm_device *dev,
+		struct drm_encoder *encoder);
+#else
+static inline void __init msm_wb_register(void) {}
+static inline void __exit msm_wb_unregister(void) {}
+static inline int msm_wb_modeset_init(struct msm_wb *wb, struct drm_device *dev,
+		struct drm_encoder *encoder) { return -EINVAL; }
+#endif
+
 #ifdef CONFIG_DEBUG_FS
 void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m);
 void msm_gem_describe_objects(struct list_head *list, struct seq_file *m);
diff --git a/drivers/gpu/drm/msm/msm_fbdev.c b/drivers/gpu/drm/msm/msm_fbdev.c
index 95f6532..1a9ae28 100644
--- a/drivers/gpu/drm/msm/msm_fbdev.c
+++ b/drivers/gpu/drm/msm/msm_fbdev.c
@@ -213,6 +213,38 @@  static void msm_crtc_fb_gamma_get(struct drm_crtc *crtc,
 	DBG("fbdev: get gamma");
 }
 
+/* add all connectors to fb except wb connector */
+static int msm_drm_fb_add_connectors(struct drm_fb_helper *fb_helper)
+{
+	struct drm_device *dev = fb_helper->dev;
+	struct drm_connector *connector;
+	int i;
+
+	list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+		struct drm_fb_helper_connector *fb_helper_connector;
+
+		if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL)
+			continue;
+
+		fb_helper_connector =
+			kzalloc(sizeof(*fb_helper_connector), GFP_KERNEL);
+		if (!fb_helper_connector)
+			goto fail;
+
+		fb_helper_connector->connector = connector;
+		fb_helper->connector_info[fb_helper->connector_count++] =
+			fb_helper_connector;
+	}
+	return 0;
+fail:
+	for (i = 0; i < fb_helper->connector_count; i++) {
+		kfree(fb_helper->connector_info[i]);
+		fb_helper->connector_info[i] = NULL;
+	}
+	fb_helper->connector_count = 0;
+	return -ENOMEM;
+}
+
 static const struct drm_fb_helper_funcs msm_fb_helper_funcs = {
 	.gamma_set = msm_crtc_fb_gamma_set,
 	.gamma_get = msm_crtc_fb_gamma_get,
@@ -242,7 +274,7 @@  struct drm_fb_helper *msm_fbdev_init(struct drm_device *dev)
 		goto fail;
 	}
 
-	ret = drm_fb_helper_single_add_all_connectors(helper);
+	ret = msm_drm_fb_add_connectors(helper);
 	if (ret)
 		goto fini;