/* Copyright (c) 2014-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. * */ #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}; /** * @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)); 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; mdss_mdp_csc_setup(MDSS_MDP_BLOCK_CDM, cdm->num, data->csc_type); if (data->csc_type == MDSS_MDP_CSC_RGB2YUV_601L) { 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; }