(()() On Tue, Apr 7, 2015 at 2:09 PM, Jilai Wang <jilaiw@xxxxxxxxxxxxxx> wrote: > Add writeback support in msm kms framework. > V1: Initial change > V2: Address Rob/Paul/Emil's comments > > Signed-off-by: Jilai Wang <jilaiw@xxxxxxxxxxxxxx> > --- > 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-arm-msm" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html