diff --git a/Documentation/devicetree/bindings/fb/mdss-mdp.txt b/Documentation/devicetree/bindings/fb/mdss-mdp.txt index f7b2ffb4a396..5c0f383d6a5a 100644 --- a/Documentation/devicetree/bindings/fb/mdss-mdp.txt +++ b/Documentation/devicetree/bindings/fb/mdss-mdp.txt @@ -408,7 +408,13 @@ Fudge Factors: Fudge factors are used to boost demand for qcom,mdss-pipe-dma-off - qcom,mdss-has-dst-split: Boolean property to indicate if destination split feature is available or not in the target. - +- qcom,mdss-cdm-off: Array of offset addresses for the available + chroma down modules that can convert RGB data + to YUV before sending it to the interface + block. These offsets will be calculated from + register "mdp_phys" define in reg property. The + number of cdm offsets should reflect the number + of cdm blocks present in hardware. Optional subnodes: Child nodes representing the frame buffer virtual devices. @@ -599,6 +605,7 @@ Example: 0x00017100 0x00019100>; qcom,mdss-intf-off = <0x00021100 0x00021300 0x00021500 0x00021700>; + qcom,mdss-cdm-off = <0x0007A200>; /* buffer parameters to calculate prefill bandwidth */ qcom,mdss-prefill-outstanding-buffer-bytes = <1024>; diff --git a/drivers/video/fbdev/msm/Makefile b/drivers/video/fbdev/msm/Makefile index 61f0a4cc71e6..bf350ebdeec1 100644 --- a/drivers/video/fbdev/msm/Makefile +++ b/drivers/video/fbdev/msm/Makefile @@ -13,6 +13,7 @@ mdss-mdp-objs += mdss_mdp_rotator.o mdss-mdp-objs += mdss_mdp_overlay.o mdss-mdp-objs += mdss_mdp_splash_logo.o mdss-mdp-objs += mdss_mdp_wb.o +mdss-mdp-objs += mdss_mdp_cdm.o obj-$(CONFIG_FB_MSM_MDSS) += mdss-mdp.o obj-$(CONFIG_FB_MSM_MDSS) += mdss_mdp_debug.o diff --git a/drivers/video/fbdev/msm/mdss.h b/drivers/video/fbdev/msm/mdss.h index b0739ec74405..e3a36450579b 100644 --- a/drivers/video/fbdev/msm/mdss.h +++ b/drivers/video/fbdev/msm/mdss.h @@ -268,6 +268,10 @@ struct mdss_data_type { u64 ab[MDSS_MAX_BUS_CLIENTS]; u64 ib[MDSS_MAX_BUS_CLIENTS]; struct mdss_pp_block_off pp_block_off; + + struct mdss_mdp_cdm *cdm_off; + u32 ncdm; + struct mutex cdm_lock; }; extern struct mdss_data_type *mdss_res; diff --git a/drivers/video/fbdev/msm/mdss_mdp.c b/drivers/video/fbdev/msm/mdss_mdp.c index 281bedf0c574..b90ab3e1850d 100644 --- a/drivers/video/fbdev/msm/mdss_mdp.c +++ b/drivers/video/fbdev/msm/mdss_mdp.c @@ -170,6 +170,7 @@ static int mdss_mdp_parse_dt_prefill(struct platform_device *pdev); static int mdss_mdp_parse_dt_misc(struct platform_device *pdev); static int mdss_mdp_parse_dt_ad_cfg(struct platform_device *pdev); static int mdss_mdp_parse_dt_bus_scale(struct platform_device *pdev); +static int mdss_mdp_parse_dt_cdm(struct platform_device *pdev); /** * mdss_mdp_vbif_axi_halt() - Halt MDSS AXI ports @@ -1723,6 +1724,10 @@ static int mdss_mdp_parse_dt(struct platform_device *pdev) return rc; } + rc = mdss_mdp_parse_dt_cdm(pdev); + if (rc) + pr_debug("CDM offset not found in device tree\n"); + /* Parse the mdp specific register base offset*/ rc = of_property_read_u32(pdev->dev.of_node, "qcom,mdss-mdp-reg-offset", &data); @@ -2253,6 +2258,75 @@ dspp_alloc_fail: return rc; } +static int mdss_mdp_cdm_addr_setup(struct mdss_data_type *mdata, + u32 *cdm_offsets, u32 len) +{ + struct mdss_mdp_cdm *head; + u32 i = 0; + + head = devm_kzalloc(&mdata->pdev->dev, sizeof(struct mdss_mdp_cdm) * + len, GFP_KERNEL); + if (!head) { + pr_err("%s: no memory for CDM info\n", __func__); + return -ENOMEM; + } + + for (i = 0; i < len; i++) { + head[i].num = i; + head[i].base = (mdata->mdss_io.base) + cdm_offsets[i]; + atomic_set(&head[i].kref.refcount, 0); + mutex_init(&head[i].lock); + pr_debug("%s: cdm off (%d) = %p\n", __func__, i, head[i].base); + } + + mdata->cdm_off = head; + mutex_init(&mdata->cdm_lock); + return 0; +} + +static int mdss_mdp_parse_dt_cdm(struct platform_device *pdev) +{ + int rc = 0; + u32 *cdm_offsets = NULL; + struct mdss_data_type *mdata = platform_get_drvdata(pdev); + + mdata->ncdm = mdss_mdp_parse_dt_prop_len(pdev, "qcom,mdss-cdm-off"); + + if (!mdata->ncdm) { + rc = 0; + pr_debug("%s: No CDM offsets present in DT\n", __func__); + goto end; + } + pr_debug("%s: cdm len == %d\n", __func__, mdata->ncdm); + cdm_offsets = kzalloc(sizeof(u32) * mdata->ncdm, GFP_KERNEL); + if (!cdm_offsets) { + pr_err("no more memory for cdm offsets\n"); + rc = -ENOMEM; + mdata->ncdm = 0; + goto end; + } + + rc = mdss_mdp_parse_dt_handler(pdev, "qcom,mdss-cdm-off", cdm_offsets, + mdata->ncdm); + if (rc) { + pr_err("device tree err: failed to get cdm offsets\n"); + goto fail; + } + + rc = mdss_mdp_cdm_addr_setup(mdata, cdm_offsets, mdata->ncdm); + if (rc) { + pr_err("%s: CDM address setup failed\n", __func__); + goto fail; + } + +fail: + kfree(cdm_offsets); + if (rc) + mdata->ncdm = 0; +end: + return rc; +} + static int mdss_mdp_parse_dt_ctl(struct platform_device *pdev) { int rc = 0; diff --git a/drivers/video/fbdev/msm/mdss_mdp.h b/drivers/video/fbdev/msm/mdss_mdp.h index c0c90b0fa989..a87660e56549 100644 --- a/drivers/video/fbdev/msm/mdss_mdp.h +++ b/drivers/video/fbdev/msm/mdss_mdp.h @@ -25,6 +25,7 @@ #include "mdss.h" #include "mdss_mdp_hwio.h" #include "mdss_fb.h" +#include "mdss_mdp_cdm.h" #define MDSS_MDP_DEFAULT_INTR_MASK 0 #define MDSS_MDP_CURSOR_WIDTH 64 @@ -107,6 +108,7 @@ enum mdss_mdp_block_type { MDSS_MDP_BLOCK_MIXER, MDSS_MDP_BLOCK_DSPP, MDSS_MDP_BLOCK_WB, + MDSS_MDP_BLOCK_CDM, MDSS_MDP_BLOCK_MAX }; @@ -224,6 +226,7 @@ struct mdss_mdp_ctl { struct msm_fb_data_type *mfd; struct mdss_mdp_mixer *mixer_left; struct mdss_mdp_mixer *mixer_right; + struct mdss_mdp_cdm *cdm; struct mutex lock; struct mutex *shared_lock; spinlock_t spin_lock; @@ -739,6 +742,21 @@ static inline int mdss_mdp_is_ubwc_supported(struct mdss_data_type *mdata) return IS_MDSS_MAJOR_MINOR_SAME(mdata->mdp_rev, MDSS_MDP_HW_REV_107); } +static inline int mdss_mdp_is_cdm_supported(struct mdss_data_type *mdata, + u32 intf_type, u32 mixer_type) +{ + int support = mdata->ncdm; + + /* + * CDM is supported under these conditions + * 1. If Device tree created a cdm block AND + * 2. Output interface is HDMI OR Output interface is WB2 + */ + return support && ((intf_type == MDSS_INTF_HDMI) || + ((intf_type == MDSS_MDP_NO_INTF) && + (mixer_type == MDSS_MDP_MIXER_TYPE_INTF))); +} + irqreturn_t mdss_mdp_isr(int irq, void *ptr); int mdss_iommu_attach(struct mdss_data_type *mdata); int mdss_iommu_dettach(struct mdss_data_type *mdata); diff --git a/drivers/video/fbdev/msm/mdss_mdp_cdm.c b/drivers/video/fbdev/msm/mdss_mdp_cdm.c new file mode 100644 index 000000000000..7c3ca6125589 --- /dev/null +++ b/drivers/video/fbdev/msm/mdss_mdp_cdm.c @@ -0,0 +1,386 @@ +/* Copyright (c) 2014, 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. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include + +#include "mdss_fb.h" +#include "mdss_mdp.h" +#include "mdss_mdp_trace.h" +#include "mdss_debug.h" + +static u32 cdm_cdwn2_cosite_h_coeff[] = {0x00000016, 0x000001cc, 0x0100009e}; +static u32 cdm_cdwn2_offsite_h_coeff[] = {0x000b0005, 0x01db01eb, 0x00e40046}; +static u32 cdm_cdwn2_cosite_v_coeff[] = {0x00080004}; +static u32 cdm_cdwn2_offsite_v_coeff[] = {0x00060002}; + +/* Limited Range rgb2yuv coeff with clamp and bias values for CSC 10 module */ +static struct mdp_csc_cfg cdm_rgb2yuv_coeff = { + 0, + { + 0x0083, 0x0102, 0x0032, + 0x1fb5, 0x1f6c, 0x00e1, + 0x00e1, 0x1f45, 0x1fdc + }, + { 0x00, 0x00, 0x00 }, + { 0x0040, 0x0200, 0x0200 }, + { 0x000, 0x3ff, 0x000, 0x3ff, 0x000, 0x3ff }, + { 0x040, 0x3ac, 0x040, 0x3c0, 0x040, 0x3c0 }, +}; + +/** + * @mdss_mdp_cdm_alloc() - Allocates a cdm block by parsing the list of + * available cdm blocks. + * + * @mdata - structure containing the list of cdm blocks + */ +static struct mdss_mdp_cdm *mdss_mdp_cdm_alloc(struct mdss_data_type *mdata) +{ + struct mdss_mdp_cdm *cdm = NULL; + u32 i = 0; + + mutex_lock(&mdata->cdm_lock); + + for (i = 0; i < mdata->ncdm; i++) { + cdm = mdata->cdm_off + i; + if (atomic_read(&cdm->kref.refcount) == 0) { + kref_init(&cdm->kref); + cdm->mdata = mdata; + pr_debug("alloc cdm=%d\n", cdm->num); + break; + } + cdm = NULL; + } + + mutex_unlock(&mdata->cdm_lock); + + return cdm; +} + +/** + * @mdss_mdp_cdm_free() - Adds the CDM block back to the available list + * @kref: Reference count structure + */ +static void mdss_mdp_cdm_free(struct kref *kref) +{ + struct mdss_mdp_cdm *cdm = container_of(kref, struct mdss_mdp_cdm, + kref); + if (!cdm) + return; + + pr_debug("free cdm_num = %d\n", cdm->num); + +} + +/** + * @mdss_mdp_cdm_init() - Allocates a CDM block and initializes the hardware + * and software context. This should be called once at + * when setting up the usecase and released when done. + * @ctl: Pointer to the control structure. + * @intf_type: Output interface which will be connected to CDM. + */ +struct mdss_mdp_cdm *mdss_mdp_cdm_init(struct mdss_mdp_ctl *ctl, u32 intf_type) +{ + struct mdss_data_type *mdata = ctl->mdata; + struct mdss_mdp_cdm *cdm = NULL; + + cdm = mdss_mdp_cdm_alloc(mdata); + + if (!cdm) { + pr_err("%s: Unable to allocate cdm\n", __func__); + return ERR_PTR(-EBUSY); + } + + cdm->out_intf = intf_type; + cdm->is_bypassed = true; + memset(&cdm->setup, 0x0, sizeof(struct mdp_cdm_cfg)); + + /* + * Setup RGB to YUV conversion in CDM. CDM will be in bypass mode for + * other cases + */ + mdss_mdp_csc_setup_data(MDSS_MDP_BLOCK_CDM, cdm->num, + &cdm_rgb2yuv_coeff); + + return cdm; +} + +/** + * @mdss_mdp_cdm_csc_setup - Programs the CSC block. + * @cdm: Pointer to the CDM structure. + * @data: Pointer to the structure containing configuration + * data. + */ +static int mdss_mdp_cdm_csc_setup(struct mdss_mdp_cdm *cdm, + struct mdp_cdm_cfg *data) +{ + int rc = 0; + u32 op_mode = 0; + + if (data->csc_type == MDSS_MDP_CSC_RGB2YUV) { + op_mode |= BIT(2); /* DST_DATA_FORMAT = YUV */ + op_mode &= ~BIT(1); /* SRC_DATA_FORMAT = RGB */ + op_mode |= BIT(0); /* EN = 1 */ + cdm->is_bypassed = false; + } else { + op_mode = 0; + cdm->is_bypassed = true; + } + + writel_relaxed(op_mode, cdm->base + MDSS_MDP_REG_CDM_CSC_10_OPMODE); + + return rc; +} + +/** + * @mdss_mdp_cdm_cdwn_setup - Programs the chroma down block. + * @cdm: Pointer to the CDM structure. + * @data: Pointer to the structure containing configuration + * data. + */ +static int mdss_mdp_cdm_cdwn_setup(struct mdss_mdp_cdm *cdm, + struct mdp_cdm_cfg *data) +{ + int rc = 0; + u32 opmode = 0; + u32 out_size = 0; + if (data->mdp_csc_bit_depth == MDP_CDM_CSC_10BIT) + opmode &= ~BIT(7); + else + opmode |= BIT(7); + + /* ENABLE DWNS_H bit */ + opmode |= BIT(1); + + switch (data->horz_downsampling_type) { + case MDP_CDM_CDWN_DISABLE: + /* CLEAR METHOD_H field */ + opmode &= ~(0x18); + /* CLEAR DWNS_H bit */ + opmode &= ~BIT(1); + break; + case MDP_CDM_CDWN_PIXEL_DROP: + /* Clear METHOD_H field (pixel drop is 0) */ + opmode &= ~(0x18); + break; + case MDP_CDM_CDWN_AVG: + /* Clear METHOD_H field (Average is 0x1) */ + opmode &= ~(0x18); + opmode |= (0x1 << 0x3); + break; + case MDP_CDM_CDWN_COSITE: + /* Clear METHOD_H field (Average is 0x2) */ + opmode &= ~(0x18); + opmode |= (0x2 << 0x3); + /* Co-site horizontal coefficients */ + writel_relaxed(cdm_cdwn2_cosite_h_coeff[0], cdm->base + + MDSS_MDP_REG_CDM_CDWN2_COEFF_COSITE_H_0); + writel_relaxed(cdm_cdwn2_cosite_h_coeff[1], cdm->base + + MDSS_MDP_REG_CDM_CDWN2_COEFF_COSITE_H_1); + writel_relaxed(cdm_cdwn2_cosite_h_coeff[2], cdm->base + + MDSS_MDP_REG_CDM_CDWN2_COEFF_COSITE_H_2); + break; + case MDP_CDM_CDWN_OFFSITE: + /* Clear METHOD_H field (Average is 0x3) */ + opmode &= ~(0x18); + opmode |= (0x3 << 0x3); + + /* Off-site horizontal coefficients */ + writel_relaxed(cdm_cdwn2_offsite_h_coeff[0], cdm->base + + MDSS_MDP_REG_CDM_CDWN2_COEFF_OFFSITE_H_0); + writel_relaxed(cdm_cdwn2_offsite_h_coeff[1], cdm->base + + MDSS_MDP_REG_CDM_CDWN2_COEFF_OFFSITE_H_1); + writel_relaxed(cdm_cdwn2_offsite_h_coeff[2], cdm->base + + MDSS_MDP_REG_CDM_CDWN2_COEFF_OFFSITE_H_2); + break; + default: + pr_err("%s invalid horz down sampling type\n", __func__); + return -EINVAL; + } + + /* ENABLE DWNS_V bit */ + opmode |= BIT(2); + + switch (data->vert_downsampling_type) { + case MDP_CDM_CDWN_DISABLE: + /* CLEAR METHOD_V field */ + opmode &= ~(0x60); + /* CLEAR DWNS_V bit */ + opmode &= ~BIT(2); + break; + case MDP_CDM_CDWN_PIXEL_DROP: + /* Clear METHOD_V field (pixel drop is 0) */ + opmode &= ~(0x60); + break; + case MDP_CDM_CDWN_AVG: + /* Clear METHOD_V field (Average is 0x1) */ + opmode &= ~(0x60); + opmode |= (0x1 << 0x5); + break; + case MDP_CDM_CDWN_COSITE: + /* Clear METHOD_V field (Average is 0x2) */ + opmode &= ~(0x60); + opmode |= (0x2 << 0x5); + /* Co-site vertical coefficients */ + writel_relaxed(cdm_cdwn2_cosite_v_coeff[0], cdm->base + + MDSS_MDP_REG_CDM_CDWN2_COEFF_COSITE_V); + break; + case MDP_CDM_CDWN_OFFSITE: + /* Clear METHOD_V field (Average is 0x3) */ + opmode &= ~(0x60); + opmode |= (0x3 << 0x5); + + /* Off-site vertical coefficients */ + writel_relaxed(cdm_cdwn2_offsite_v_coeff[0], cdm->base + + MDSS_MDP_REG_CDM_CDWN2_COEFF_OFFSITE_V); + break; + default: + pr_err("%s invalid vert down sampling type\n", __func__); + return -EINVAL; + } + + if (data->vert_downsampling_type || data->horz_downsampling_type) + opmode |= BIT(0); /* EN CDWN module */ + else + opmode &= ~BIT(0); + + out_size = (data->output_width & 0xFFFF) | + ((data->output_height & 0xFFFF) << 16); + writel_relaxed(out_size, cdm->base + MDSS_MDP_REG_CDM_CDWN2_OUT_SIZE); + writel_relaxed(opmode, cdm->base + MDSS_MDP_REG_CDM_CDWN2_OP_MODE); + writel_relaxed(((0x3FF << 16) | 0x0), + cdm->base + MDSS_MDP_REG_CDM_CDWN2_CLAMP_OUT); + return rc; + +} + +/** + * @mdss_mdp_cdm_out_packer_setup - Programs the output packer block. + * @cdm: Pointer to the CDM structure. + * @data: Pointer to the structure containing + * configuration data. + */ +static int mdss_mdp_cdm_out_packer_setup(struct mdss_mdp_cdm *cdm, + struct mdp_cdm_cfg *data) +{ + int rc = 0; + u32 opmode = 0; + u32 cdm_enable = 0; + struct mdss_mdp_format_params *fmt; + + if (cdm->out_intf == MDP_CDM_CDWN_OUTPUT_HDMI) { + /* Enable HDMI packer */ + opmode |= BIT(0); + fmt = mdss_mdp_get_format_params(data->out_format); + if (!fmt) { + pr_err("cdm format = %d, not supported\n", + data->out_format); + return -EINVAL; + } + opmode &= ~0x6; + opmode |= (fmt->chroma_sample << 1); + if (!cdm->is_bypassed) + cdm_enable |= BIT(19); + + } else { + /* Disable HDMI pacler for WB */ + opmode = 0; + if (!cdm->is_bypassed) + cdm_enable |= BIT(24); + } + writel_relaxed(cdm_enable, cdm->mdata->mdp_base + + MDSS_MDP_MDP_OUT_CTL_0); + writel_relaxed(opmode, cdm->base + MDSS_MDP_REG_CDM_HDMI_PACK_OP_MODE); + + return rc; +} + +/** + * @mdss_mdp_cdm_setup - Sets up the CDM block based on the usecase. The CDM + * block should be initialized before calling this + * function. + * @cdm: Pointer to the CDM structure. + * @data: Pointer to the structure containing configuration + * data. + */ +int mdss_mdp_cdm_setup(struct mdss_mdp_cdm *cdm, struct mdp_cdm_cfg *data) +{ + int rc = 0; + + if (!cdm || !data) { + pr_err("%s: invalid arguments\n", __func__); + return -EINVAL; + } + + mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); + mutex_lock(&cdm->lock); + /* Setup CSC block */ + rc = mdss_mdp_cdm_csc_setup(cdm, data); + if (rc) { + pr_err("%s: csc configuration failure\n", __func__); + goto fail; + } + + /* Setup chroma down sampler */ + rc = mdss_mdp_cdm_cdwn_setup(cdm, data); + if (rc) { + pr_err("%s: cdwn configuration failure\n", __func__); + goto fail; + } + + /* Setup HDMI packer */ + rc = mdss_mdp_cdm_out_packer_setup(cdm, data); + if (rc) { + pr_err("%s: out packer configuration failure\n", __func__); + goto fail; + } + + memcpy(&cdm->setup, data, sizeof(struct mdp_cdm_cfg)); + +fail: + mutex_unlock(&cdm->lock); + mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); + return rc; +} + +/** + * @mdss_mdp_cdm_destroy - Destroys the CDM configuration and return it to + * default state. + * @cdm: Pointer to the CDM structure + */ +int mdss_mdp_cdm_destroy(struct mdss_mdp_cdm *cdm) +{ + int rc = 0; + + if (!cdm) { + pr_err("%s: invalid parameters\n", __func__); + return -EINVAL; + } + + mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_ON); + mutex_lock(&cdm->lock); + /* Disable HDMI packer */ + writel_relaxed(0x0, cdm->base + MDSS_MDP_REG_CDM_HDMI_PACK_OP_MODE); + + /* Put CDM in bypass */ + writel_relaxed(0x0, cdm->mdata->mdp_base + MDSS_MDP_MDP_OUT_CTL_0); + + mutex_unlock(&cdm->lock); + mdss_mdp_clk_ctrl(MDP_BLOCK_POWER_OFF); + + kref_put(&cdm->kref, mdss_mdp_cdm_free); + + return rc; +} diff --git a/drivers/video/fbdev/msm/mdss_mdp_cdm.h b/drivers/video/fbdev/msm/mdss_mdp_cdm.h new file mode 100644 index 000000000000..4515628da0e7 --- /dev/null +++ b/drivers/video/fbdev/msm/mdss_mdp_cdm.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2014, 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 MDSS_MDP_CDM_H +#define MDSS_MDP_CDM_H + +#include +#include + +enum mdp_cdm_cdwn_method_type { + MDP_CDM_CDWN_DISABLE, + MDP_CDM_CDWN_PIXEL_DROP, + MDP_CDM_CDWN_AVG, + MDP_CDM_CDWN_COSITE, + MDP_CDM_CDWN_OFFSITE, +}; + +enum mdp_cdm_cdwn_output_type { + MDP_CDM_CDWN_OUTPUT_HDMI, + MDP_CDM_CDWN_OUTPUT_WB, +}; + +enum mdp_cdm_csc_bit_depth { + MDP_CDM_CSC_8BIT, + MDP_CDM_CSC_10BIT, +}; + +struct mdp_cdm_cfg { + /* CSC block configuration */ + u32 mdp_csc_bit_depth; + u32 csc_type; + /* CDWN block configuration */ + u32 horz_downsampling_type; + u32 vert_downsampling_type; + /* Output packer configuration */ + u32 output_width; + u32 output_height; + u32 out_format; +}; + +struct mdss_mdp_cdm { + u32 num; + char __iomem *base; + struct kref kref; + struct mutex lock; + + struct mdss_data_type *mdata; + u32 out_intf; + bool is_bypassed; + struct mdp_cdm_cfg setup; +}; + +struct mdss_mdp_cdm *mdss_mdp_cdm_init(struct mdss_mdp_ctl *ctl, + u32 intf_type); +int mdss_mdp_cdm_destroy(struct mdss_mdp_cdm *cdm); +int mdss_mdp_cdm_setup(struct mdss_mdp_cdm *cdm, struct mdp_cdm_cfg *data); + +#endif /* MDSS_MDP_CDM_H */ diff --git a/drivers/video/fbdev/msm/mdss_mdp_hwio.h b/drivers/video/fbdev/msm/mdss_mdp_hwio.h index c30b8ea3e208..983f1c37ed36 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_hwio.h +++ b/drivers/video/fbdev/msm/mdss_mdp_hwio.h @@ -623,6 +623,27 @@ enum mdss_mdp_pingpong_index { #define MDSS_MDP_INTF_MISR_CTRL 0x180 #define MDSS_MDP_INTF_MISR_SIGNATURE (MDSS_MDP_INTF_MISR_CTRL + 0x4) +#define MDSS_MDP_REG_CDM_CSC_10_OPMODE 0x000 +#define MDSS_MDP_REG_CDM_CSC_10_BASE 0x004 + +#define MDSS_MDP_REG_CDM_CDWN2_OP_MODE 0x100 +#define MDSS_MDP_REG_CDM_CDWN2_CLAMP_OUT 0x104 +#define MDSS_MDP_REG_CDM_CDWN2_PARAMS_3D_0 0x108 +#define MDSS_MDP_REG_CDM_CDWN2_PARAMS_3D_1 0x10C +#define MDSS_MDP_REG_CDM_CDWN2_COEFF_COSITE_H_0 0x110 +#define MDSS_MDP_REG_CDM_CDWN2_COEFF_COSITE_H_1 0x114 +#define MDSS_MDP_REG_CDM_CDWN2_COEFF_COSITE_H_2 0x118 +#define MDSS_MDP_REG_CDM_CDWN2_COEFF_OFFSITE_H_0 0x11C +#define MDSS_MDP_REG_CDM_CDWN2_COEFF_OFFSITE_H_1 0x120 +#define MDSS_MDP_REG_CDM_CDWN2_COEFF_OFFSITE_H_2 0x124 +#define MDSS_MDP_REG_CDM_CDWN2_COEFF_COSITE_V 0x128 +#define MDSS_MDP_REG_CDM_CDWN2_COEFF_OFFSITE_V 0x12C +#define MDSS_MDP_REG_CDM_CDWN2_OUT_SIZE 0x130 + +#define MDSS_MDP_REG_CDM_HDMI_PACK_OP_MODE 0x200 + +/* Following offsets are with respect to MDP base */ +#define MDSS_MDP_MDP_OUT_CTL_0 0x410 /* following offsets are with respect to MDP VBIF base */ #define MMSS_VBIF_XIN_HALT_CTRL0 0x200 #define MMSS_VBIF_XIN_HALT_CTRL1 0x204 diff --git a/drivers/video/fbdev/msm/mdss_mdp_intf_video.c b/drivers/video/fbdev/msm/mdss_mdp_intf_video.c index 3fc9fdfd416b..f77667f7133f 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_intf_video.c +++ b/drivers/video/fbdev/msm/mdss_mdp_intf_video.c @@ -309,6 +309,17 @@ static int mdss_mdp_video_timegen_setup(struct mdss_mdp_ctl *ctl, mdp_video_write(ctx, MDSS_MDP_REG_INTF_POLARITY_CTL, polarity_ctl); mdp_video_write(ctx, MDSS_MDP_REG_INTF_FRAME_LINE_COUNT_EN, 0x3); + /* + * If CDM is present Interface should have destination + * format set to RGB + */ + if (ctl->cdm) { + u32 reg = mdp_video_read(ctx, MDSS_MDP_REG_INTF_CONFIG); + + reg &= ~BIT(18); /* CSC_DST_DATA_FORMAT = RGB */ + reg &= ~BIT(17); /* CSC_SRC_DATA_FROMAT = RGB */ + mdp_video_write(ctx, MDSS_MDP_REG_INTF_CONFIG, reg); + } return 0; } @@ -499,6 +510,10 @@ static int mdss_mdp_video_stop(struct mdss_mdp_ctl *ctl, int panel_power_state) mdss_mdp_ctl_reset(ctl); ctl->priv_data = NULL; + if (ctl->cdm) { + mdss_mdp_cdm_destroy(ctl->cdm); + ctl->cdm = NULL; + } return 0; } @@ -1092,6 +1107,49 @@ static void mdss_mdp_fetch_start_config(struct mdss_mdp_video_ctx *ctx, mdp_video_write(ctx, MDSS_MDP_REG_INTF_CONFIG, fetch_enable); } +static int mdss_mdp_video_cdm_setup(struct mdss_mdp_cdm *cdm, + struct mdss_panel_info *pinfo) +{ + struct mdss_mdp_format_params *fmt; + struct mdp_cdm_cfg setup; + + fmt = mdss_mdp_get_format_params(pinfo->out_format); + + if (!fmt) { + pr_err("%s: format %d not supported\n", __func__, + pinfo->out_format); + return -EINVAL; + } + setup.out_format = pinfo->out_format; + if (fmt->is_yuv) + setup.csc_type = MDSS_MDP_CSC_RGB2YUV; + else + setup.csc_type = MDSS_MDP_CSC_RGB2RGB; + + switch (fmt->chroma_sample) { + case MDSS_MDP_CHROMA_RGB: + setup.horz_downsampling_type = MDP_CDM_CDWN_DISABLE; + setup.vert_downsampling_type = MDP_CDM_CDWN_DISABLE; + break; + case MDSS_MDP_CHROMA_H2V1: + setup.horz_downsampling_type = MDP_CDM_CDWN_COSITE; + setup.vert_downsampling_type = MDP_CDM_CDWN_DISABLE; + break; + case MDSS_MDP_CHROMA_420: + setup.horz_downsampling_type = MDP_CDM_CDWN_COSITE; + setup.vert_downsampling_type = MDP_CDM_CDWN_OFFSITE; + break; + case MDSS_MDP_CHROMA_H1V2: + default: + pr_err("%s: unsupported chroma sampling type\n", __func__); + return -EINVAL; + } + + setup.mdp_csc_bit_depth = MDP_CDM_CSC_8BIT; + setup.output_width = pinfo->xres + pinfo->lcdc.xres_pad; + setup.output_height = pinfo->yres + pinfo->lcdc.yres_pad; + return mdss_mdp_cdm_setup(cdm, &setup); +} static int mdss_mdp_video_intfs_setup(struct mdss_mdp_ctl *ctl, struct mdss_panel_data *pdata, int inum) { @@ -1151,6 +1209,23 @@ static int mdss_mdp_video_intfs_setup(struct mdss_mdp_ctl *ctl, ctx->intf_recovery.data = NULL; } + if (mdss_mdp_is_cdm_supported(mdata, ctl->intf_type, 0)) { + ctl->cdm = mdss_mdp_cdm_init(ctl, MDP_CDM_CDWN_OUTPUT_HDMI); + if (ctl->cdm) { + if (mdss_mdp_video_cdm_setup(ctl->cdm, pinfo)) { + pr_err("%s: setting up cdm failed\n", + __func__); + return -EINVAL; + } + ctl->flush_bits |= BIT(26); + } else { + pr_err("%s: failed to initialize cdm\n", __func__); + return -EINVAL; + } + } else { + pr_debug("%s: cdm not supported\n", __func__); + } + mdss_mdp_set_intr_callback(MDSS_MDP_IRQ_INTF_VSYNC, (inum + MDSS_MDP_INTF0), mdss_mdp_video_vsync_intr_done, ctl); @@ -1175,7 +1250,18 @@ static int mdss_mdp_video_intfs_setup(struct mdss_mdp_ctl *ctl, itp.v_front_porch = pinfo->lcdc.v_front_porch; itp.hsync_pulse_width = pinfo->lcdc.h_pulse_width; itp.vsync_pulse_width = pinfo->lcdc.v_pulse_width; - + /* + * In case of YUV420 output, MDP outputs data at half the rate. So + * reduce all horizontal parameters by half + */ + if (ctl->cdm && pinfo->out_format == MDP_Y_CBCR_H2V2) { + itp.width >>= 1; + itp.hsync_skew >>= 1; + itp.xres >>= 1; + itp.h_back_porch >>= 1; + itp.h_front_porch >>= 1; + itp.hsync_pulse_width >>= 1; + } if (!ctl->panel_data->panel_info.cont_splash_enabled) { if (mdss_mdp_video_timegen_setup(ctl, &itp)) { pr_err("unable to set timing parameters intfs: %d\n", diff --git a/drivers/video/fbdev/msm/mdss_mdp_intf_writeback.c b/drivers/video/fbdev/msm/mdss_mdp_intf_writeback.c index 445ae5e970e3..e43e985af385 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_intf_writeback.c +++ b/drivers/video/fbdev/msm/mdss_mdp_intf_writeback.c @@ -138,8 +138,51 @@ static int mdss_mdp_writeback_addr_setup(struct mdss_mdp_writeback_ctx *ctx, return 0; } +static int mdss_mdp_writeback_cdm_setup(struct mdss_mdp_writeback_ctx *ctx, + struct mdss_mdp_cdm *cdm, u32 format) +{ + struct mdss_mdp_format_params *fmt; + struct mdp_cdm_cfg setup; + + fmt = mdss_mdp_get_format_params(format); + if (!fmt) { + pr_err("%s: format %d not supported\n", __func__, format); + return -EINVAL; + } + + if (fmt->is_yuv) + setup.csc_type = MDSS_MDP_CSC_RGB2YUV; + else + setup.csc_type = MDSS_MDP_CSC_RGB2RGB; + + switch (fmt->chroma_sample) { + case MDSS_MDP_CHROMA_RGB: + setup.horz_downsampling_type = MDP_CDM_CDWN_DISABLE; + setup.vert_downsampling_type = MDP_CDM_CDWN_DISABLE; + break; + case MDSS_MDP_CHROMA_H2V1: + setup.horz_downsampling_type = MDP_CDM_CDWN_COSITE; + setup.vert_downsampling_type = MDP_CDM_CDWN_DISABLE; + break; + case MDSS_MDP_CHROMA_420: + setup.horz_downsampling_type = MDP_CDM_CDWN_COSITE; + setup.vert_downsampling_type = MDP_CDM_CDWN_OFFSITE; + break; + case MDSS_MDP_CHROMA_H1V2: + default: + pr_err("%s: unsupported chroma sampling type\n", __func__); + return -EINVAL; + } + + setup.out_format = format; + setup.mdp_csc_bit_depth = MDP_CDM_CSC_8BIT; + setup.output_width = ctx->width; + setup.output_height = ctx->height; + return mdss_mdp_cdm_setup(cdm, &setup); +} + static int mdss_mdp_writeback_format_setup(struct mdss_mdp_writeback_ctx *ctx, - u32 format) + u32 format, struct mdss_mdp_ctl *ctl) { struct mdss_mdp_format_params *fmt; u32 dst_format, pattern, ystride0, ystride1, outsize, chroma_samp; @@ -147,6 +190,7 @@ static int mdss_mdp_writeback_format_setup(struct mdss_mdp_writeback_ctx *ctx, u32 opmode = ctx->opmode; bool rotation = false; struct mdss_data_type *mdata; + int rc; pr_debug("wb_num=%d format=%d\n", ctx->wb_num, format); @@ -167,7 +211,18 @@ static int mdss_mdp_writeback_format_setup(struct mdss_mdp_writeback_ctx *ctx, chroma_samp = fmt->chroma_sample; - if (ctx->type != MDSS_MDP_WRITEBACK_TYPE_ROTATOR && fmt->is_yuv) { + if (ctl->cdm) { + + rc = mdss_mdp_writeback_cdm_setup(ctx, ctl->cdm, format); + if (rc) { + pr_err("%s: CDM configuration failed with error %d\n", + __func__, rc); + return rc; + } + ctl->flush_bits |= BIT(26); + } + if (ctx->type != MDSS_MDP_WRITEBACK_TYPE_ROTATOR && + fmt->is_yuv && !ctl->cdm) { mdss_mdp_csc_setup(MDSS_MDP_BLOCK_WB, ctx->wb_num, MDSS_MDP_CSC_RGB2YUV); opmode |= (1 << 8) | /* CSC_EN */ @@ -233,7 +288,8 @@ static int mdss_mdp_writeback_format_setup(struct mdss_mdp_writeback_ctx *ctx, outsize = (ctx->dst_rect.h << 16) | ctx->dst_rect.w; if (mdss_mdp_is_ubwc_format(fmt)) { - opmode |= BIT(0); + if (!ctl->cdm) + opmode |= BIT(0); dst_format |= BIT(31); } @@ -245,7 +301,8 @@ static int mdss_mdp_writeback_format_setup(struct mdss_mdp_writeback_ctx *ctx, } mdp_wb_write(ctx, MDSS_MDP_REG_WB_ALPHA_X_VALUE, 0xFF); mdp_wb_write(ctx, MDSS_MDP_REG_WB_DST_FORMAT, dst_format); - mdp_wb_write(ctx, MDSS_MDP_REG_WB_DST_OP_MODE, opmode); + if (!ctl->cdm) + mdp_wb_write(ctx, MDSS_MDP_REG_WB_DST_OP_MODE, opmode); mdp_wb_write(ctx, MDSS_MDP_REG_WB_DST_PACK_PATTERN, pattern); mdp_wb_write(ctx, MDSS_MDP_REG_WB_DST_YSTRIDE0, ystride0); mdp_wb_write(ctx, MDSS_MDP_REG_WB_DST_YSTRIDE1, ystride1); @@ -276,7 +333,7 @@ static int mdss_mdp_writeback_prepare_wfd(struct mdss_mdp_ctl *ctl, void *arg) ctx->dst_rect.w = ctx->width; ctx->dst_rect.h = ctx->height; - ret = mdss_mdp_writeback_format_setup(ctx, ctl->dst_format); + ret = mdss_mdp_writeback_format_setup(ctx, ctl->dst_format, ctl); if (ret) { pr_err("format setup failed\n"); return ret; @@ -348,7 +405,7 @@ static int mdss_mdp_writeback_prepare_rot(struct mdss_mdp_ctl *ctl, void *arg) if (ctx->rot90) ctx->opmode |= BIT(5); /* ROT 90 */ - return mdss_mdp_writeback_format_setup(ctx, format); + return mdss_mdp_writeback_format_setup(ctx, format, ctl); } static int mdss_mdp_wb_add_vsync_handler(struct mdss_mdp_ctl *ctl, @@ -428,6 +485,10 @@ static int mdss_mdp_writeback_stop(struct mdss_mdp_ctl *ctl, ctx->ref_cnt--; } + if (ctl->cdm) { + mdss_mdp_cdm_destroy(ctl->cdm); + ctl->cdm = NULL; + } return 0; } @@ -686,6 +747,7 @@ int mdss_mdp_writeback_start(struct mdss_mdp_ctl *ctl) { struct mdss_mdp_writeback_ctx *ctx; u32 mem_sel; + u32 mixer_type = 0; pr_debug("start ctl=%d\n", ctl->num); @@ -701,6 +763,21 @@ int mdss_mdp_writeback_start(struct mdss_mdp_ctl *ctl) pr_err("invalid writeback mode %d\n", mem_sel); return -EINVAL; } + if (ctl->mixer_left) + mixer_type = ctl->mixer_left->type; + else + mixer_type = MDSS_MDP_MIXER_TYPE_UNUSED; + + if (mdss_mdp_is_cdm_supported(ctl->mdata, ctl->intf_type, + ctl->mixer_left->type)) { + ctl->cdm = mdss_mdp_cdm_init(ctl, MDP_CDM_CDWN_OUTPUT_WB); + if (!ctl->cdm) { + pr_err("%s failed to init cdm\n", __func__); + return -EBUSY; + } + } else { + pr_debug("%s: cdm not supported\n", __func__); + } ctl->priv_data = ctx; ctx->wb_num = ctl->num; /* wb num should match ctl num */ ctx->base = ctl->wb_base; diff --git a/drivers/video/fbdev/msm/mdss_mdp_pp.c b/drivers/video/fbdev/msm/mdss_mdp_pp.c index 408790d783ad..4e97c3849bcf 100644 --- a/drivers/video/fbdev/msm/mdss_mdp_pp.c +++ b/drivers/video/fbdev/msm/mdss_mdp_pp.c @@ -410,6 +410,7 @@ int mdss_mdp_csc_setup_data(u32 block, u32 blk_idx, struct mdp_csc_cfg *data) struct mdss_data_type *mdata; struct mdss_mdp_pipe *pipe; struct mdss_mdp_ctl *ctl; + struct mdss_mdp_cdm *cdm; if (data == NULL) { @@ -445,6 +446,18 @@ int mdss_mdp_csc_setup_data(u32 block, u32 blk_idx, struct mdp_csc_cfg *data) ret = -EINVAL; } break; + case MDSS_MDP_BLOCK_CDM: + if (blk_idx < mdata->ncdm) { + cdm = mdata->cdm_off + blk_idx; + if (cdm->base) + base = cdm->base + + MDSS_MDP_REG_CDM_CSC_10_BASE; + else + ret = -EINVAL; + } else { + ret = -EINVAL; + } + break; default: ret = -EINVAL; break;