blob: ab680f5576f9dcbfc1f0c14ce75e8ef829da2055 [file] [log] [blame]
/* Copyright (c) 2014-2016, 2018, 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 <linux/errno.h>
#include <linux/mutex.h>
#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};
#define VSYNC_TIMEOUT_US 16000
/**
* @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;
complete_all(&cdm->free_comp);
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);
/**
* give hdmi interface priority to alloc the cdm block. It will wait
* for one vsync cycle to allow wfd to finish its job and try to reserve
* the block the again.
*/
if (!cdm && (intf_type == MDP_CDM_CDWN_OUTPUT_HDMI)) {
/* always wait for first cdm block */
cdm = mdata->cdm_off;
if (cdm) {
reinit_completion(&cdm->free_comp);
/*
* no need to check the return status of completion
* timeout. Next cdm_alloc call will try to reserve
* the cdm block and returns failure if allocation
* fails.
*/
wait_for_completion_timeout(&cdm->free_comp,
usecs_to_jiffies(VSYNC_TIMEOUT_US));
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) ||
(data->csc_type == MDSS_MDP_CSC_RGB2YUV_601FR) ||
(data->csc_type == MDSS_MDP_CSC_RGB2YUV_709L)) {
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;
}
kref_put(&cdm->kref, mdss_mdp_cdm_free);
return rc;
}