[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAF6AEGuSSdcrAELiid8uWHAfX1ApAxj00mvQOcj5Zmkhkyxv5A@mail.gmail.com>
Date: Wed, 19 Aug 2015 15:00:04 -0400
From: Rob Clark <robdclark@...il.com>
To: Jilai Wang <jilaiw@...eaurora.org>
Cc: "dri-devel@...ts.freedesktop.org" <dri-devel@...ts.freedesktop.org>,
linux-arm-msm <linux-arm-msm@...r.kernel.org>,
Linux Kernel Mailing List <linux-kernel@...r.kernel.org>
Subject: Re: [PATCH 2/3] drm:msm: Initial Add Writeback Support (V2)
(()()
On Tue, Apr 7, 2015 at 2:09 PM, Jilai Wang <jilaiw@...eaurora.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@...eaurora.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
>
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists