Re: [PATCH 2/3] drm:msm: Initial Add Writeback Support

Rob Clark
2015-04-02T14:30:04Z

So, from a quick look, it seems like there is a lot of potential to
split the v4l part out into some drm helpers.. it looks pretty
generic(ish), or at least it could be with some strategically placed
vfuncs in drm_v4l2_helper_funcs.

I do think we need to figure out the auth/security situation. We
probably don't want to let arbitrary processes open a v4l device and
snoop on the screen contents. We perhaps could re-use the dri2 drm
auth stuff (v4l2_drm_get_magic ioctl?). Or, well, it would be nice if
the wb device could be made to not exist in /dev at all, and
pre-open'd fd returned from an ioctl on the drm device, but not really
sure if that is possible (or too weird). Once the compositor process
has the v4l device open and authenticated somehow, I expect it would
use fd passing to pass the fd off to a trusted helper process.

BR,
-R

On Wed, Apr 1, 2015 at 5:12 PM, Jilai Wang <jilaiw@codeaurora.org> wrote:
> Add writeback support in msm kms framework.
>
> Signed-off-by: Jilai Wang <jilaiw@codeaurora.org>
> ---
> drivers/gpu/drm/msm/Kconfig | 10 +
> drivers/gpu/drm/msm/Makefile | 9 +-
> 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 | 19 +-
> drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h | 7 +
> drivers/gpu/drm/msm/mdp/mdp5/mdp5_wb_encoder.c | 460 +++++++++++++++++++
> drivers/gpu/drm/msm/mdp/mdp_kms.h | 2 +-
> drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.c | 319 +++++++++++++
> 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 | 522 ++++++++++++++++++++++
> drivers/gpu/drm/msm/msm_drv.c | 2 +
> drivers/gpu/drm/msm/msm_drv.h | 19 +-
> drivers/gpu/drm/msm/msm_fbdev.c | 34 +-
> drivers/gpu/drm/msm/msm_gem.c | 1 +
> 16 files changed, 1664 insertions(+), 6 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 1e6a907..f6c7914 100644
> --- a/drivers/gpu/drm/msm/Kconfig
> +++ b/drivers/gpu/drm/msm/Kconfig
> @@ -27,6 +27,16 @@ config DRM_MSM_FBDEV
> support. Note that this support also provide the linux console
> support on top of the MSM modesetting driver.
>
> +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.
> +
> config DRM_MSM_REGISTER_LOGGING
> bool "MSM DRM register logging"
> depends on DRM_MSM
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index 674a132..e5bf334 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-y := -Iinclude/drm -Idrivers/gpu/drm/msm -Idrivers/gpu/drm/msm/mdp_wb
> +ccflags-$(CONFIG_DRM_MSM_WB) += -Idrivers/gpu/drm/msm/mdp/mdp_wb
>
> msm-y := \
> adreno/adreno_device.o \
> @@ -51,4 +52,10 @@ msm-y := \
> msm-$(CONFIG_DRM_MSM_FBDEV) += msm_fbdev.o
> msm-$(CONFIG_COMMON_CLK) += mdp/mdp4/mdp4_lvds_pll.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 1fe7315..e87cf74 100644
> --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.c
> @@ -71,9 +71,14 @@ 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,
> [3] = INTF_HDMI,
> + [4] = INTF_WB,
> },
> .max_clk = 200000000,
> };
> @@ -135,9 +140,14 @@ 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,
> [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 f47328d..ccb6048c 100644
> --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h
> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_cfg.h
> @@ -74,6 +74,7 @@ struct mdp5_cfg_hw {
> struct mdp5_sub_block dspp;
> struct mdp5_sub_block ad;
> 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 ff9201b..1b1569d 100644
> --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c
> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.c
> @@ -179,7 +179,11 @@ static struct drm_encoder *construct_encoder(struct mdp5_kms *mdp5_kms,
> .mode = intf_mode,
> };
>
> - encoder = mdp5_encoder_init(dev, &intf);
> + if (intf_type == INTF_WB)
> + encoder = mdp5_wb_encoder_init(dev, &intf);
> + else
> + encoder = mdp5_encoder_init(dev, &intf);
> +
> if (IS_ERR(encoder)) {
> dev_err(dev->dev, "failed to construct encoder\n");
> return encoder;
> @@ -230,6 +234,19 @@ static int modeset_init_intf(struct mdp5_kms *mdp5_kms, int intf_num)
>
> ret = hdmi_modeset_init(priv->hdmi, dev, encoder);
> 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 6efa5c6..f6d23cc 100644
> --- a/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_kms.h
> @@ -238,5 +238,12 @@ struct drm_crtc *mdp5_crtc_init(struct drm_device *dev,
>
> struct drm_encoder *mdp5_encoder_init(struct drm_device *dev,
> struct mdp5_interface *intf);
> +#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 NULL; }
> +#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..8ce3449
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/mdp/mdp5/mdp5_wb_encoder.c
> @@ -0,0 +1,460 @@
> +/* 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..5c40ec9
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb.c
> @@ -0,0 +1,319 @@
> +/* 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;
> + goto fail;
> + }
> +
> + priv->connectors[priv->num_connectors++] = wb->connector;
> +
> + return 0;
> +
> +fail:
> + if (wb->connector) {
> + wb->connector->funcs->destroy(wb->connector);
> + wb->connector = NULL;
> + }
> +
> + return ret;
> +}
> +
> +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..4c75419
> --- /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 __WB_CONNECTOR_H__
> +#define __WB_CONNECTOR_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 /* __WB_CONNECTOR_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..f6df4d4
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/mdp/mdp_wb/mdp_wb_v4l2.c
> @@ -0,0 +1,522 @@
> +/* 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/module.h>
> +#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 MSM_WB_MODULE_NAME "msm_wb"
> +#define MAX_WIDTH 2048
> +#define MAX_HEIGHT 2048
> +
> +static unsigned debug;
> +module_param(debug, uint, 0644);
> +MODULE_PARM_DESC(debug, "activates debug info");
> +
> +#define dprintk(dev, level, fmt, arg...) \
> + v4l2_dbg(level, debug, &dev->v4l2_dev, fmt, ## arg)
> +
> +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)
> + break;
> + }
> +
> + if (k == ARRAY_SIZE(formats))
> + return NULL;
> +
> + return &formats[k];
> +}
> +
> +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;
> +
> + mutex_lock(&drm_dev->object_name_lock);
> + obj = drm_dev->driver->gem_prime_import(drm_dev, dbuf);
> + if (WARN_ON(!obj)) {
> + mutex_unlock(&drm_dev->object_name_lock);
> + v4l2_err(&dev->v4l2_dev, "Can't convert dmabuf to gem obj.\n");
> + return NULL;
> + }
> +
> + if (obj->dma_buf) {
> + if (WARN_ON(obj->dma_buf != dbuf)) {
> + v4l2_err(&dev->v4l2_dev,
> + "dma buf doesn't match.\n");
> + drm_gem_object_unreference_unlocked(obj);
> +
> + obj = ERR_PTR(-EINVAL);
> + }
> + } else {
> + obj->dma_buf = dbuf;
> + }
> +
> + mutex_unlock(&drm_dev->object_name_lock);
> +
> + 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;
> + }
> +
> + dprintk(dev, 1, "%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;
> +
> + dprintk(dev, 1, "%s\n", __func__);
> +
> + /* 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);
> +
> + dprintk(dev, 1, "%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);
> +
> + dprintk(dev, 1, "%s\n", __func__);
> +
> + return msm_wb_stop_streaming(dev->wb);
> +}
> +
> +static const struct vb2_ops msm_wb_vb2_ops = {
> + .queue_setup = msm_wb_vb2_queue_setup,
> + .buf_prepare = msm_wb_vb2_buf_prepare,
> + .buf_queue = msm_wb_vb2_buf_queue,
> + .start_streaming = msm_wb_vb2_start_streaming,
> + .stop_streaming = msm_wb_vb2_stop_streaming,
> +};
> +
> +/* ------------------------------------------------------------------
> + IOCTL vidioc handling
> + ------------------------------------------------------------------*/
> +static int msm_wb_vidioc_querycap(struct file *file, void *priv,
> + struct v4l2_capability *cap)
> +{
> + struct msm_wb_v4l2_dev *dev = video_drvdata(file);
> +
> + strcpy(cap->driver, "msm_wb");
> + strcpy(cap->card, "msm_wb");
> + snprintf(cap->bus_info, sizeof(cap->bus_info),
> + "platform:%s", dev->v4l2_dev.name);
> + cap->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING;
> + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
> +
> + return 0;
> +}
> +
> +static int msm_wb_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_fmtdesc *f)
> +{
> + struct msm_wb_v4l2_dev *dev = video_drvdata(file);
> + const struct msm_wb_fmt *fmt;
> +
> + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
> + v4l2_err(&dev->v4l2_dev, "Invalid buf type %d.\n",
> + f->type);
> + return -EINVAL;
> + }
> +
> + if (f->index >= ARRAY_SIZE(formats))
> + return -ERANGE;
> +
> + fmt = &formats[f->index];
> +
> + strlcpy(f->description, fmt->name, sizeof(f->description));
> + f->pixelformat = fmt->fourcc;
> +
> + return 0;
> +}
> +
> +static int msm_wb_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct msm_wb_v4l2_dev *dev = video_drvdata(file);
> + int i;
> +
> + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> + f->fmt.pix_mp.width = dev->width;
> + f->fmt.pix_mp.height = dev->height;
> + f->fmt.pix_mp.field = V4L2_FIELD_NONE;
> + f->fmt.pix_mp.pixelformat = dev->fmt->fourcc;
> + f->fmt.pix_mp.num_planes = dev->fmt->plane_cnt;
> +
> + for (i = 0; i < dev->fmt->plane_cnt; i++) {
> + f->fmt.pix_mp.plane_fmt[i].bytesperline =
> + (dev->fmt->plane_bpp[i] * dev->width) >> 3;
> + f->fmt.pix_mp.plane_fmt[i].sizeimage =
> + f->fmt.pix_mp.plane_fmt[i].bytesperline * dev->height;
> + }
> +
> + if (dev->fmt->is_yuv)
> + f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SMPTE170M;
> + else
> + f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SRGB;
> +
> + return 0;
> +}
> +
> +static int msm_wb_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct msm_wb_v4l2_dev *dev = video_drvdata(file);
> + const struct msm_wb_fmt *fmt;
> + int i;
> +
> + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
> + v4l2_err(&dev->v4l2_dev, "Invalid buf type %d.\n",
> + f->type);
> + return -EINVAL;
> + }
> +
> + fmt = get_format(f->fmt.pix_mp.pixelformat);
> + if (!fmt) {
> + v4l2_err(&dev->v4l2_dev, "Fourcc format (0x%08x) unknown.\n",
> + f->fmt.pix_mp.pixelformat);
> + return -ENOTSUPP;
> + }
> +
> + f->fmt.pix_mp.field = V4L2_FIELD_NONE;
> + v4l_bound_align_image(&f->fmt.pix_mp.width, 48, MAX_WIDTH, 4,
> + &f->fmt.pix_mp.height, 32, MAX_HEIGHT, 4, 0);
> + f->fmt.pix_mp.num_planes = fmt->plane_cnt;
> +
> + for (i = 0; i < dev->fmt->plane_cnt; i++) {
> + f->fmt.pix_mp.plane_fmt[i].bytesperline =
> + (dev->fmt->plane_bpp[i] * f->fmt.pix_mp.width) >> 3;
> + f->fmt.pix_mp.plane_fmt[i].sizeimage =
> + f->fmt.pix_mp.plane_fmt[i].bytesperline *
> + f->fmt.pix_mp.height;
> + }
> +
> + if (fmt->is_yuv)
> + f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SMPTE170M;
> + else
> + f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_SRGB;
> +
> + return 0;
> +}
> +
> +static int msm_wb_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
> + struct v4l2_format *f)
> +{
> + struct msm_wb_v4l2_dev *dev = video_drvdata(file);
> + struct msm_wb *wb = dev->wb;
> + struct vb2_queue *q = &dev->vb_vidq;
> + int rc;
> +
> + rc = msm_wb_vidioc_try_fmt_vid_cap(file, priv, f);
> + if (rc < 0)
> + return rc;
> +
> + if (vb2_is_busy(q)) {
> + v4l2_err(&dev->v4l2_dev, "%s device busy\n", __func__);
> + return -EBUSY;
> + }
> +
> + dev->fmt = get_format(f->fmt.pix_mp.pixelformat);
> + dev->width = f->fmt.pix_mp.width;
> + dev->height = f->fmt.pix_mp.height;
> +
> + rc = msm_wb_set_buf_format(wb, dev->fmt->drm_fourcc,
> + dev->width, dev->height);
> + if (rc)
> + v4l2_err(&dev->v4l2_dev,
> + "Set format (0x%08x w:%x h:%x) failed.\n",
> + dev->fmt->drm_fourcc, dev->width, dev->height);
> +
> + return rc;
> +}
> +
> +static const struct v4l2_file_operations msm_wb_v4l2_fops = {
> + .owner = THIS_MODULE,
> + .open = v4l2_fh_open,
> + .release = vb2_fop_release,
> + .poll = vb2_fop_poll,
> + .unlocked_ioctl = video_ioctl2,
> +};
> +
> +static const struct v4l2_ioctl_ops msm_wb_v4l2_ioctl_ops = {
> + .vidioc_querycap = msm_wb_vidioc_querycap,
> + .vidioc_enum_fmt_vid_cap_mplane = msm_wb_vidioc_enum_fmt_vid_cap,
> + .vidioc_g_fmt_vid_cap_mplane = msm_wb_vidioc_g_fmt_vid_cap,
> + .vidioc_try_fmt_vid_cap_mplane = msm_wb_vidioc_try_fmt_vid_cap,
> + .vidioc_s_fmt_vid_cap_mplane = msm_wb_vidioc_s_fmt_vid_cap,
> + .vidioc_reqbufs = vb2_ioctl_reqbufs,
> + .vidioc_querybuf = vb2_ioctl_querybuf,
> + .vidioc_qbuf = vb2_ioctl_qbuf,
> + .vidioc_dqbuf = vb2_ioctl_dqbuf,
> + .vidioc_streamon = vb2_ioctl_streamon,
> + .vidioc_streamoff = vb2_ioctl_streamoff,
> + .vidioc_log_status = v4l2_ctrl_log_status,
> + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
> + .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +};
> +
> +static const struct video_device msm_wb_v4l2_template = {
> + .name = "msm_wb",
> + .fops = &msm_wb_v4l2_fops,
> + .ioctl_ops = &msm_wb_v4l2_ioctl_ops,
> + .release = video_device_release_empty,
> +};
> +
> +int msm_wb_v4l2_init(struct msm_wb *wb)
> +{
> + struct msm_wb_v4l2_dev *dev;
> + struct video_device *vfd;
> + struct vb2_queue *q;
> + int ret;
> +
> + dev = kzalloc(sizeof(*dev), GFP_KERNEL);
> + if (!dev)
> + return -ENOMEM;
> +
> + snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name),
> + "%s", MSM_WB_MODULE_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->debug = debug;
> + 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 a72ed0a..61e76d0 100644
> --- a/drivers/gpu/drm/msm/msm_drv.c
> +++ b/drivers/gpu/drm/msm/msm_drv.c
> @@ -1032,6 +1032,7 @@ static struct platform_driver msm_platform_driver = {
> static int __init msm_drm_register(void)
> {
> DBG("init");
> + msm_wb_register();
> msm_edp_register();
> hdmi_register();
> adreno_register();
> @@ -1045,6 +1046,7 @@ static void __exit msm_drm_unregister(void)
> hdmi_unregister();
> adreno_unregister();
> msm_edp_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 9e8d441..ceb551a 100644
> --- a/drivers/gpu/drm/msm/msm_drv.h
> +++ b/drivers/gpu/drm/msm/msm_drv.h
> @@ -53,6 +53,9 @@ struct msm_mmu;
> struct msm_rd_state;
> struct msm_perf_state;
> struct msm_gem_submit;
> +struct hdmi;
> +struct msm_edp;
> +struct msm_wb;
>
> #define NUM_DOMAINS 2 /* one for KMS, then one per gpu core (?) */
>
> @@ -82,6 +85,8 @@ struct msm_drm_private {
> */
> struct msm_edp *edp;
>
> + 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;
> @@ -224,18 +229,28 @@ struct drm_framebuffer *msm_framebuffer_create(struct drm_device *dev,
>
> struct drm_fb_helper *msm_fbdev_init(struct drm_device *dev);
>
> -struct hdmi;
> int hdmi_modeset_init(struct hdmi *hdmi, struct drm_device *dev,
> struct drm_encoder *encoder);
> void __init hdmi_register(void);
> void __exit hdmi_unregister(void);
>
> -struct msm_edp;
> void __init msm_edp_register(void);
> void __exit msm_edp_unregister(void);
> int msm_edp_modeset_init(struct msm_edp *edp, struct drm_device *dev,
> struct drm_encoder *encoder);
>
> +#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 df60f65..9d553f2 100644
> --- a/drivers/gpu/drm/msm/msm_fbdev.c
> +++ b/drivers/gpu/drm/msm/msm_fbdev.c
> @@ -212,6 +212,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,
> @@ -241,7 +273,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;
>
> diff --git a/drivers/gpu/drm/msm/msm_gem.c b/drivers/gpu/drm/msm/msm_gem.c
> index 49dea4f..11a1924 100644
> --- a/drivers/gpu/drm/msm/msm_gem.c
> +++ b/drivers/gpu/drm/msm/msm_gem.c
> @@ -534,6 +534,7 @@ void msm_gem_free_object(struct drm_gem_object *obj)
> if (msm_obj->pages)
> drm_free_large(msm_obj->pages);
>
> + drm_prime_gem_destroy(obj, msm_obj->sgt);
> } else {
> vunmap(msm_obj->vaddr);
> put_pages(obj);
> --
> The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum,
> a Linux Foundation Collaborative Project
>