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