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

Rob Clark robdclark at gmail.com
Wed Aug 19 12:00:04 PDT 2015


(()()

On Tue, Apr 7, 2015 at 2:09 PM, Jilai Wang <jilaiw at 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 at 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
>


More information about the dri-devel mailing list