blob: d39592007e70c85b14fd0cc9eb26236060cebf4d [file] [log] [blame]
/* Copyright (c) 2015-2017, 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) "[drm:%s:%d] " fmt, __func__, __LINE__
#include <linux/slab.h>
#include <linux/of_address.h>
#include "sde_hw_mdss.h"
#include "sde_hw_catalog.h"
#include "sde_hw_catalog_format.h"
#include "sde_kms.h"
/*************************************************************
* MACRO DEFINITION
*************************************************************/
/**
* Max hardware block in certain hardware. For ex: sspp pipes
* can have QSEED, pcc, igc, pa, csc, etc. This count is max
* 12 based on software design. It should be increased if any of the
* hardware block has more subblocks.
*/
#define MAX_SDE_HW_BLK 12
/* each entry will have register address and bit offset in that register */
#define MAX_BIT_OFFSET 2
/* default line width for sspp */
#define DEFAULT_SDE_LINE_WIDTH 2048
/* max mixer blend stages */
#define DEFAULT_SDE_MIXER_BLENDSTAGES 7
/* max bank bit for macro tile and ubwc format */
#define DEFAULT_SDE_HIGHEST_BANK_BIT 15
/* default hardware block size if dtsi entry is not present */
#define DEFAULT_SDE_HW_BLOCK_LEN 0x100
/* default rects for multi rect case */
#define DEFAULT_SDE_SSPP_MAX_RECTS 1
/* total number of intf - dp, dsi, hdmi */
#define INTF_COUNT 3
#define MAX_SSPP_UPSCALE 20
#define MAX_SSPP_DOWNSCALE 4
#define SSPP_UNITY_SCALE 1
#define MAX_HORZ_DECIMATION 4
#define MAX_VERT_DECIMATION 4
#define MAX_SPLIT_DISPLAY_CTL 2
#define MAX_PP_SPLIT_DISPLAY_CTL 1
#define MDSS_BASE_OFFSET 0x0
#define ROT_LM_OFFSET 3
#define LINE_LM_OFFSET 5
#define LINE_MODE_WB_OFFSET 2
/*************************************************************
* DTSI PROPERTY INDEX
*************************************************************/
enum {
HW_OFF,
HW_LEN,
};
enum sde_prop {
SDE_OFF,
SDE_LEN,
SSPP_LINEWIDTH,
MIXER_LINEWIDTH,
MIXER_BLEND,
WB_LINEWIDTH,
BANK_BIT,
QSEED_TYPE,
PANIC_PER_PIPE,
CDP,
};
enum {
SSPP_OFF,
SSPP_SIZE,
SSPP_TYPE,
SSPP_XIN,
SSPP_CLK_CTRL,
SSPP_CLK_STATUS,
SSPP_DANGER,
SSPP_SAFE,
SSPP_MAX_RECTS,
SSPP_SCALE_SIZE,
SSPP_QSEED_OFF,
SSPP_CSC_OFF,
};
enum {
INTF_OFF,
INTF_LEN,
INTF_PREFETCH,
INTF_TYPE,
};
enum {
PP_OFF,
PP_LEN,
TE_OFF,
TE_LEN,
TE2_OFF,
TE2_LEN,
DSC_OFF,
DSC_LEN,
};
enum {
DSPP_OFF,
DSPP_SIZE,
DSPP_IGC,
DSPP_PCC,
DSPP_GC,
DSPP_PA,
DSPP_GAMUT,
DSPP_DITHER,
DSPP_HIST,
DSPP_AD,
};
enum {
MIXER_OFF,
MIXER_LEN,
SRC_SPLIT,
MIXER_GC,
};
enum {
WB_OFF,
WB_LEN,
WB_XIN_ID,
};
/*************************************************************
* dts property definition
*************************************************************/
enum prop_type {
PROP_TYPE_BOOL,
PROP_TYPE_U32,
PROP_TYPE_U32_ARRAY,
PROP_TYPE_STRING,
PROP_TYPE_STRING_ARRAY,
PROP_TYPE_BIT_OFFSET_ARRAY,
};
struct sde_prop_type {
/* use property index from enum property for readability purpose */
u8 id;
/* it should be property name based on dtsi documentation */
char *prop_name;
/**
* if property is marked mandatory then it will fail parsing
* when property is not present
*/
u32 is_mandatory;
/* property type based on "enum prop_type" */
enum prop_type type;
};
/*************************************************************
* dts property list
*************************************************************/
static struct sde_prop_type sde_prop[] = {
{SDE_OFF, "qcom,sde-off", true, PROP_TYPE_U32},
{SDE_LEN, "qcom,sde-len", false, PROP_TYPE_U32},
{SSPP_LINEWIDTH, "qcom,sde-sspp-linewidth", false, PROP_TYPE_U32},
{MIXER_LINEWIDTH, "qcom,sde-mixer-linewidth", false, PROP_TYPE_U32},
{MIXER_BLEND, "qcom,sde-mixer-blendstages", false, PROP_TYPE_U32},
{WB_LINEWIDTH, "qcom,sde-wb-linewidth", false, PROP_TYPE_U32},
{BANK_BIT, "qcom,sde-highest-bank-bit", false, PROP_TYPE_U32},
{QSEED_TYPE, "qcom,sde-qseed-type", false, PROP_TYPE_STRING},
{PANIC_PER_PIPE, "qcom,sde-panic-per-pipe", false, PROP_TYPE_BOOL},
{CDP, "qcom,sde-has-cdp", false, PROP_TYPE_BOOL},
};
static struct sde_prop_type sspp_prop[] = {
{SSPP_OFF, "qcom,sde-sspp-off", true, PROP_TYPE_U32_ARRAY},
{SSPP_SIZE, "qcom,sde-sspp-src-size", false, PROP_TYPE_U32},
{SSPP_TYPE, "qcom,sde-sspp-type", true, PROP_TYPE_STRING_ARRAY},
{SSPP_XIN, "qcom,sde-sspp-xin-id", true, PROP_TYPE_U32_ARRAY},
{SSPP_CLK_CTRL, "qcom,sde-sspp-clk-ctrl", false,
PROP_TYPE_BIT_OFFSET_ARRAY},
{SSPP_CLK_STATUS, "qcom,sde-sspp-clk-status", false,
PROP_TYPE_BIT_OFFSET_ARRAY},
{SSPP_DANGER, "qcom,sde-sspp-danger-lut", false, PROP_TYPE_U32_ARRAY},
{SSPP_SAFE, "qcom,sde-sspp-safe-lut", false, PROP_TYPE_U32_ARRAY},
{SSPP_MAX_RECTS, "qcom,sde-sspp-max-rects", false, PROP_TYPE_U32_ARRAY},
{SSPP_SCALE_SIZE, "qcom,sde-sspp-scale-size", false, PROP_TYPE_U32},
{SSPP_QSEED_OFF, "qcom,sde-sspp-qseed-off", false, PROP_TYPE_U32},
{SSPP_CSC_OFF, "qcom,sde-sspp-csc-off", false, PROP_TYPE_U32},
};
static struct sde_prop_type ctl_prop[] = {
{HW_OFF, "qcom,sde-ctl-off", true, PROP_TYPE_U32_ARRAY},
{HW_LEN, "qcom,sde-ctl-size", false, PROP_TYPE_U32},
};
static struct sde_prop_type mixer_prop[] = {
{MIXER_OFF, "qcom,sde-mixer-off", true, PROP_TYPE_U32_ARRAY},
{MIXER_LEN, "qcom,sde-mixer-size", false, PROP_TYPE_U32},
{SRC_SPLIT, "qcom,sde-has-src-split", false, PROP_TYPE_BOOL},
{MIXER_GC, "qcom,sde-has-mixer-gc", false, PROP_TYPE_BOOL},
};
static struct sde_prop_type dspp_prop[] = {
{DSPP_OFF, "qcom,sde-dspp-off", true, PROP_TYPE_U32_ARRAY},
{DSPP_SIZE, "qcom,sde-dspp-size", false, PROP_TYPE_U32},
{DSPP_IGC, "qcom,sde-dspp-igc-off", false, PROP_TYPE_U32},
{DSPP_PCC, "qcom,sde-dspp-pcc-off", false, PROP_TYPE_U32},
{DSPP_GC, "qcom,sde-dspp-gc-off", false, PROP_TYPE_U32},
{DSPP_PA, "qcom,sde-dspp-pa-off", false, PROP_TYPE_U32},
{DSPP_GAMUT, "qcom,sde-dspp-gamut-off", false, PROP_TYPE_U32},
{DSPP_DITHER, "qcom,sde-dspp-dither-off", false, PROP_TYPE_U32},
{DSPP_HIST, "qcom,sde-dspp-hist-off", false, PROP_TYPE_U32},
{DSPP_AD, "qcom,sde-dspp-ad-off", false, PROP_TYPE_U32_ARRAY},
};
static struct sde_prop_type pp_prop[] = {
{PP_OFF, "qcom,sde-pp-off", true, PROP_TYPE_U32_ARRAY},
{PP_LEN, "qcom,sde-pp-size", false, PROP_TYPE_U32},
{TE_OFF, "qcom,sde-te-off", false, PROP_TYPE_U32_ARRAY},
{TE_LEN, "qcom,sde-te-size", false, PROP_TYPE_U32},
{TE2_OFF, "qcom,sde-te2-off", false, PROP_TYPE_U32_ARRAY},
{TE2_LEN, "qcom,sde-te2-size", false, PROP_TYPE_U32},
{DSC_OFF, "qcom,sde-dsc-off", false, PROP_TYPE_U32_ARRAY},
{DSC_LEN, "qcom,sde-dsc-size", false, PROP_TYPE_U32},
};
static struct sde_prop_type cdm_prop[] = {
{HW_OFF, "qcom,sde-cdm-off", false, PROP_TYPE_U32_ARRAY},
{HW_LEN, "qcom,sde-cdm-size", false, PROP_TYPE_U32},
};
static struct sde_prop_type intf_prop[] = {
{INTF_OFF, "qcom,sde-intf-off", true, PROP_TYPE_U32_ARRAY},
{INTF_LEN, "qcom,sde-intf-size", false, PROP_TYPE_U32},
{INTF_PREFETCH, "qcom,sde-intf-max-prefetch-lines", false,
PROP_TYPE_U32_ARRAY},
{INTF_TYPE, "qcom,sde-intf-type", false, PROP_TYPE_STRING_ARRAY},
};
static struct sde_prop_type wb_prop[] = {
{WB_OFF, "qcom,sde-wb-off", true, PROP_TYPE_U32_ARRAY},
{WB_LEN, "qcom,sde-wb-size", false, PROP_TYPE_U32},
{WB_XIN_ID, "qcom,sde-wb-xin-id", false, PROP_TYPE_U32_ARRAY},
};
/*************************************************************
* static API list
*************************************************************/
static int _parse_dt_u32_handler(struct device_node *np,
char *prop_name, u32 *offsets, int len, bool mandatory)
{
int rc = of_property_read_u32_array(np, prop_name, offsets, len);
if (rc && mandatory)
SDE_ERROR("mandatory prop: %s u32 array read len:%d\n",
prop_name, len);
else if (rc)
SDE_DEBUG("optional prop: %s u32 array read len:%d\n",
prop_name, len);
return rc;
}
static int _parse_dt_bit_offset(struct device_node *np,
char *prop_name, u32 prop_value[][MAX_BIT_OFFSET],
u32 count, bool mandatory)
{
int rc = 0, len, i, j;
const u32 *arr;
arr = of_get_property(np, prop_name, &len);
if (arr) {
len /= sizeof(u32);
for (i = 0, j = 0; i < len; j++) {
prop_value[j][i] = be32_to_cpu(arr[i]);
i++;
prop_value[j][i] = be32_to_cpu(arr[i]);
i++;
}
} else {
if (mandatory) {
SDE_ERROR("error mandatory property '%s' not found\n",
prop_name);
rc = -EINVAL;
} else {
SDE_DEBUG("error optional property '%s' not found\n",
prop_name);
}
}
return rc;
}
static int _validate_dt_entry(struct device_node *np,
struct sde_prop_type *sde_prop, u32 prop_size, int *prop_count,
int *off_count)
{
int rc = 0, i, val;
*off_count = of_property_count_u32_elems(np, sde_prop[0].prop_name);
if ((*off_count > MAX_BLOCKS) || (*off_count < 0)) {
SDE_ERROR("invalid hw offset prop name:%s count:%d\n",
sde_prop[0].prop_name, *off_count);
*off_count = 0;
return sde_prop[0].is_mandatory ? -EINVAL : 0;
}
for (i = 0; i < prop_size && i < MAX_BLOCKS; i++) {
switch (sde_prop[i].type) {
case PROP_TYPE_U32:
rc = of_property_read_u32(np, sde_prop[i].prop_name,
&val);
break;
case PROP_TYPE_U32_ARRAY:
prop_count[i] = of_property_count_u32_elems(np,
sde_prop[i].prop_name);
break;
case PROP_TYPE_STRING_ARRAY:
prop_count[i] = of_property_count_strings(np,
sde_prop[i].prop_name);
break;
case PROP_TYPE_BIT_OFFSET_ARRAY:
of_get_property(np, sde_prop[i].prop_name, &val);
prop_count[i] = val / (MAX_BIT_OFFSET * sizeof(u32));
break;
default:
SDE_DEBUG("invalid property type:%d\n",
sde_prop[i].type);
break;
}
SDE_DEBUG("prop id:%d prop name:%s prop type:%d \"\
prop_count:%d\n", i, sde_prop[i].prop_name,
sde_prop[i].type, prop_count[i]);
if (rc && sde_prop[i].is_mandatory &&
(sde_prop[i].type == PROP_TYPE_U32)) {
SDE_ERROR("prop:%s not present\n",
sde_prop[i].prop_name);
goto end;
} else if (sde_prop[i].type == PROP_TYPE_U32 ||
sde_prop[i].type == PROP_TYPE_BOOL) {
rc = 0;
continue;
}
if ((prop_count[i] != *off_count) && sde_prop[i].is_mandatory) {
SDE_ERROR("prop:%s count:%d is different compared to \"\
offset array:%d\n", sde_prop[i].prop_name,
prop_count[i], *off_count);
rc = -EINVAL;
goto end;
} else if (prop_count[i] != *off_count) {
SDE_DEBUG("prop:%s count:%d is different compared to \"\
offset array:%d\n", sde_prop[i].prop_name,
prop_count[i], *off_count);
rc = 0;
prop_count[i] = 0;
}
}
end:
return rc;
}
static int _read_dt_entry(struct device_node *np,
struct sde_prop_type *sde_prop, u32 prop_size, u32 *prop_count,
u32 prop_value[][MAX_SDE_HW_BLK],
u32 bit_value[][MAX_SDE_HW_BLK][MAX_BIT_OFFSET])
{
int rc = 0, i, j;
for (i = 0; i < prop_size && i < MAX_BLOCKS; i++) {
switch (sde_prop[i].type) {
case PROP_TYPE_U32:
of_property_read_u32(np, sde_prop[i].prop_name,
&prop_value[i][0]);
SDE_DEBUG("prop id:%d prop name:%s prop type:%d \"\
value:0x%x\n", i, sde_prop[i].prop_name,
sde_prop[i].type, prop_value[i][0]);
break;
case PROP_TYPE_BOOL:
prop_value[i][0] = of_property_read_bool(np,
sde_prop[i].prop_name);
SDE_DEBUG("prop id:%d prop name:%s prop type:%d \"\
value:0x%x\n", i, sde_prop[i].prop_name,
sde_prop[i].type, prop_value[i][0]);
break;
case PROP_TYPE_U32_ARRAY:
rc = _parse_dt_u32_handler(np, sde_prop[i].prop_name,
prop_value[i], prop_count[i],
sde_prop[i].is_mandatory);
if (rc && sde_prop[i].is_mandatory) {
SDE_ERROR("%s prop validation success but \"\
read failed\n", sde_prop[i].prop_name);
goto end;
} else {
/* only for debug purpose */
SDE_DEBUG("prop id:%d prop name:%s prop \"\
type:%d", i, sde_prop[i].prop_name,
sde_prop[i].type);
for (j = 0; j < prop_count[i]; j++)
SDE_DEBUG(" value[%d]:0x%x ", j,
prop_value[i][j]);
SDE_DEBUG("\n");
}
break;
case PROP_TYPE_BIT_OFFSET_ARRAY:
rc = _parse_dt_bit_offset(np, sde_prop[i].prop_name,
bit_value[i], prop_count[i],
sde_prop[i].is_mandatory);
if (rc && sde_prop[i].is_mandatory) {
SDE_ERROR("%s prop validation success but \"\
read failed\n", sde_prop[i].prop_name);
goto end;
} else {
SDE_DEBUG("prop id:%d prop name:%s prop \"\
type:%d", i, sde_prop[i].prop_name,
sde_prop[i].type);
for (j = 0; j < prop_count[i]; j++)
SDE_DEBUG(" count[%d]: bit:0x%x \"\
off:0x%x ", j, bit_value[i][j][0],
bit_value[i][j][1]);
SDE_DEBUG("\n");
}
break;
default:
SDE_DEBUG("invalid property type:%d\n",
sde_prop[i].type);
break;
}
rc = 0;
}
end:
return rc;
}
static void _sde_sspp_setup_vig(struct sde_mdss_cfg *sde_cfg,
struct sde_sspp_cfg *sspp, struct sde_sspp_sub_blks *sblk,
u32 prop_value[][MAX_SDE_HW_BLK], u32 *vig_count)
{
if (sde_cfg->qseed_type == SDE_SSPP_SCALER_QSEED2) {
set_bit(SDE_SSPP_SCALER_QSEED2, &sspp->features);
sblk->scaler_blk.id = SDE_SSPP_SCALER_QSEED2;
sblk->scaler_blk.base = prop_value[SSPP_QSEED_OFF][0];
} else if (sde_cfg->qseed_type == SDE_SSPP_SCALER_QSEED3) {
set_bit(SDE_SSPP_SCALER_QSEED3, &sspp->features);
sblk->scaler_blk.id = SDE_SSPP_SCALER_QSEED3;
sblk->scaler_blk.base = prop_value[SSPP_QSEED_OFF][0];
}
set_bit(SDE_SSPP_CSC, &sspp->features);
sblk->csc_blk.base = prop_value[SSPP_CSC_OFF][0];
sblk->csc_blk.id = SDE_SSPP_CSC;
sblk->maxupscale = MAX_SSPP_UPSCALE;
sblk->maxdwnscale = MAX_SSPP_DOWNSCALE;
sspp->id = SSPP_VIG0 + *vig_count;
sspp->clk_ctrl = SDE_CLK_CTRL_NONE;
sblk->format_list = plane_formats_yuv;
(*vig_count)++;
}
static void _sde_sspp_setup_rgb(struct sde_mdss_cfg *sde_cfg,
struct sde_sspp_cfg *sspp, struct sde_sspp_sub_blks *sblk,
u32 prop_value[][MAX_SDE_HW_BLK], u32 *rgb_count)
{
if (sde_cfg->qseed_type == SDE_SSPP_SCALER_QSEED2) {
set_bit(SDE_SSPP_SCALER_RGB, &sspp->features);
sblk->scaler_blk.id = SDE_SSPP_SCALER_QSEED2;
sblk->scaler_blk.base = prop_value[SSPP_QSEED_OFF][0];
} else if (sde_cfg->qseed_type == SDE_SSPP_SCALER_QSEED3) {
set_bit(SDE_SSPP_SCALER_RGB, &sspp->features);
sblk->scaler_blk.id = SDE_SSPP_SCALER_QSEED3;
sblk->scaler_blk.base = prop_value[SSPP_QSEED_OFF][0];
}
sblk->maxupscale = MAX_SSPP_UPSCALE;
sblk->maxdwnscale = MAX_SSPP_DOWNSCALE;
sspp->id = SSPP_RGB0 + *rgb_count;
sspp->clk_ctrl = SDE_CLK_CTRL_NONE;
sblk->format_list = plane_formats;
(*rgb_count)++;
}
static void _sde_sspp_setup_cursor(struct sde_mdss_cfg *sde_cfg,
struct sde_sspp_cfg *sspp, struct sde_sspp_sub_blks *sblk,
u32 prop_value[][MAX_SDE_HW_BLK], u32 *cursor_count)
{
set_bit(SDE_SSPP_CURSOR, &sspp->features);
sblk->maxupscale = SSPP_UNITY_SCALE;
sblk->maxdwnscale = SSPP_UNITY_SCALE;
sspp->id = SSPP_CURSOR0 + *cursor_count;
sspp->clk_ctrl = SDE_CLK_CTRL_NONE;
sblk->format_list = plane_formats;
(*cursor_count)++;
}
static void _sde_sspp_setup_dma(struct sde_mdss_cfg *sde_cfg,
struct sde_sspp_cfg *sspp, struct sde_sspp_sub_blks *sblk,
u32 prop_value[][MAX_SDE_HW_BLK], u32 *dma_count)
{
sblk->maxupscale = SSPP_UNITY_SCALE;
sblk->maxdwnscale = SSPP_UNITY_SCALE;
sspp->id = SSPP_DMA0 + *dma_count;
sspp->clk_ctrl = SDE_CLK_CTRL_NONE;
sblk->format_list = plane_formats;
(*dma_count)++;
}
static int sde_sspp_parse_dt(struct device_node *np,
struct sde_mdss_cfg *sde_cfg)
{
int rc, prop_count[MAX_BLOCKS], off_count, i;
u32 prop_value[MAX_BLOCKS][MAX_SDE_HW_BLK];
u32 bit_value[MAX_BLOCKS][MAX_SDE_HW_BLK][MAX_BIT_OFFSET];
const char *type;
struct sde_sspp_cfg *sspp;
struct sde_sspp_sub_blks *sblk;
u32 vig_count = 0, dma_count = 0, rgb_count = 0, cursor_count = 0;
rc = _validate_dt_entry(np, sspp_prop, ARRAY_SIZE(sspp_prop),
prop_count, &off_count);
if (rc)
goto end;
rc = _read_dt_entry(np, sspp_prop, ARRAY_SIZE(sspp_prop), prop_count,
prop_value, bit_value);
if (rc)
goto end;
sde_cfg->sspp_count = off_count;
for (i = 0; i < off_count; i++) {
sspp = sde_cfg->sspp + i;
sblk = kzalloc(sizeof(*sblk), GFP_KERNEL);
if (!sblk) {
rc = -ENOMEM;
/* catalog deinit will release the allocated blocks */
goto end;
}
sspp->sblk = sblk;
sspp->base = prop_value[SSPP_OFF][i];
sblk->maxlinewidth = sde_cfg->max_sspp_linewidth;
set_bit(SDE_SSPP_SRC, &sspp->features);
sblk->src_blk.id = SDE_SSPP_SRC;
of_property_read_string_index(np,
sspp_prop[SSPP_TYPE].prop_name, i, &type);
if (!strcmp(type, "vig")) {
_sde_sspp_setup_vig(sde_cfg, sspp, sblk, prop_value,
&vig_count);
} else if (!strcmp(type, "rgb")) {
_sde_sspp_setup_rgb(sde_cfg, sspp, sblk, prop_value,
&rgb_count);
} else if (!strcmp(type, "cursor")) {
_sde_sspp_setup_cursor(sde_cfg, sspp, sblk, prop_value,
&cursor_count);
} else if (!strcmp(type, "dma")) {
_sde_sspp_setup_dma(sde_cfg, sspp, sblk, prop_value,
&dma_count);
} else {
SDE_ERROR("invalid sspp type:%s\n", type);
rc = -EINVAL;
goto end;
}
sblk->maxhdeciexp = MAX_HORZ_DECIMATION;
sblk->maxvdeciexp = MAX_VERT_DECIMATION;
sspp->xin_id = prop_value[SSPP_XIN][i];
sblk->danger_lut_linear = prop_value[SSPP_DANGER][i];
sblk->safe_lut_linear = prop_value[SSPP_SAFE][i];
sblk->src_blk.len = prop_value[SSPP_SIZE][0];
}
end:
return rc;
}
static int sde_ctl_parse_dt(struct device_node *np,
struct sde_mdss_cfg *sde_cfg)
{
int rc, prop_count[MAX_BLOCKS], i;
u32 prop_value[MAX_BLOCKS][MAX_SDE_HW_BLK] = { {0} };
u32 bit_value[MAX_BLOCKS][MAX_SDE_HW_BLK][MAX_BIT_OFFSET]
= { { { 0 } } };
struct sde_ctl_cfg *ctl;
u32 off_count;
if (!sde_cfg) {
SDE_ERROR("invalid argument input param\n");
rc = -EINVAL;
goto end;
}
rc = _validate_dt_entry(np, ctl_prop, ARRAY_SIZE(ctl_prop), prop_count,
&off_count);
if (rc)
goto end;
sde_cfg->ctl_count = off_count;
rc = _read_dt_entry(np, ctl_prop, ARRAY_SIZE(ctl_prop), prop_count,
prop_value, bit_value);
if (rc)
goto end;
for (i = 0; i < off_count; i++) {
ctl = sde_cfg->ctl + i;
ctl->base = prop_value[HW_OFF][i];
ctl->id = CTL_0 + i;
if (i < MAX_SPLIT_DISPLAY_CTL)
set_bit(SDE_CTL_SPLIT_DISPLAY, &ctl->features);
if (i < MAX_PP_SPLIT_DISPLAY_CTL)
set_bit(SDE_CTL_PINGPONG_SPLIT, &ctl->features);
}
end:
return rc;
}
static int sde_mixer_parse_dt(struct device_node *np,
struct sde_mdss_cfg *sde_cfg)
{
int rc, prop_count[MAX_BLOCKS], i;
u32 prop_value[MAX_BLOCKS][MAX_SDE_HW_BLK] = { { 0 } };
u32 bit_value[MAX_BLOCKS][MAX_SDE_HW_BLK][MAX_BIT_OFFSET]
= { { { 0 } } };
u32 off_count, max_blendstages;
u32 blend_reg_base[] = {0x20, 0x50, 0x80, 0xb0, 0x230, 0x260, 0x290};
u32 lm_pair_mask[] = {LM_1, LM_0, LM_5, 0x0, 0x0, LM_2};
struct sde_lm_cfg *mixer;
struct sde_lm_sub_blks *sblk;
int pp_count, dspp_count;
u32 pp_idx, dspp_idx;
if (!sde_cfg) {
SDE_ERROR("invalid argument input param\n");
rc = -EINVAL;
goto end;
}
max_blendstages = sde_cfg->max_mixer_blendstages;
rc = _validate_dt_entry(np, mixer_prop, ARRAY_SIZE(mixer_prop),
prop_count, &off_count);
if (rc)
goto end;
sde_cfg->mixer_count = off_count;
rc = _read_dt_entry(np, mixer_prop, ARRAY_SIZE(mixer_prop), prop_count,
prop_value, bit_value);
if (rc)
goto end;
pp_count = sde_cfg->pingpong_count;
dspp_count = sde_cfg->dspp_count;
for (i = 0, pp_idx = 0, dspp_idx = 0; i < off_count; i++) {
mixer = sde_cfg->mixer + i;
sblk = kzalloc(sizeof(*sblk), GFP_KERNEL);
if (!sblk) {
rc = -ENOMEM;
/* catalog deinit will release the allocated blocks */
goto end;
}
mixer->sblk = sblk;
mixer->base = prop_value[HW_OFF][i];
mixer->len = prop_value[HW_LEN][0];
mixer->id = LM_0 + i;
if (!mixer->len)
mixer->len = DEFAULT_SDE_HW_BLOCK_LEN;
if (lm_pair_mask[i])
mixer->lm_pair_mask = 1 << lm_pair_mask[i];
sblk->maxblendstages = max_blendstages;
sblk->maxwidth = sde_cfg->max_mixer_width;
memcpy(sblk->blendstage_base, blend_reg_base, sizeof(u32) *
min_t(u32, MAX_BLOCKS, min_t(u32,
ARRAY_SIZE(blend_reg_base), max_blendstages)));
if (prop_value[SRC_SPLIT][0])
set_bit(SDE_MIXER_SOURCESPLIT, &mixer->features);
if (prop_value[MIXER_GC][0])
set_bit(SDE_MIXER_GC, &mixer->features);
if ((i < ROT_LM_OFFSET) || (i >= LINE_LM_OFFSET)) {
mixer->pingpong = pp_count > 0 ? pp_idx + PINGPONG_0
: PINGPONG_MAX;
mixer->dspp = dspp_count > 0 ? dspp_idx + DSPP_0
: DSPP_MAX;
pp_count--;
dspp_count--;
pp_idx++;
dspp_idx++;
} else {
mixer->pingpong = PINGPONG_MAX;
mixer->dspp = DSPP_MAX;
}
}
end:
return rc;
}
static int sde_intf_parse_dt(struct device_node *np,
struct sde_mdss_cfg *sde_cfg)
{
int rc, prop_count[MAX_BLOCKS], i;
u32 prop_value[MAX_BLOCKS][MAX_SDE_HW_BLK] = { { 0 } };
u32 bit_value[MAX_BLOCKS][MAX_SDE_HW_BLK][MAX_BIT_OFFSET]
= { { { 0 } } };
u32 off_count;
u32 dsi_count = 0, none_count = 0, hdmi_count = 0, dp_count = 0;
const char *type;
struct sde_intf_cfg *intf;
if (!sde_cfg) {
SDE_ERROR("invalid argument\n");
rc = -EINVAL;
goto end;
}
rc = _validate_dt_entry(np, intf_prop, ARRAY_SIZE(intf_prop),
prop_count, &off_count);
if (rc)
goto end;
sde_cfg->intf_count = off_count;
rc = _read_dt_entry(np, intf_prop, ARRAY_SIZE(intf_prop), prop_count,
prop_value, bit_value);
if (rc)
goto end;
for (i = 0; i < off_count; i++) {
intf = sde_cfg->intf + i;
intf->base = prop_value[INTF_OFF][i];
intf->len = prop_value[INTF_LEN][0];
intf->id = INTF_0 + i;
if (!intf->len)
intf->len = DEFAULT_SDE_HW_BLOCK_LEN;
intf->prog_fetch_lines_worst_case =
prop_value[INTF_PREFETCH][i];
of_property_read_string_index(np,
intf_prop[INTF_TYPE].prop_name, i, &type);
if (!strcmp(type, "dsi")) {
intf->type = INTF_DSI;
intf->controller_id = dsi_count;
dsi_count++;
} else if (!strcmp(type, "hdmi")) {
intf->type = INTF_HDMI;
intf->controller_id = hdmi_count;
hdmi_count++;
} else if (!strcmp(type, "dp")) {
intf->type = INTF_DP;
intf->controller_id = dp_count;
dp_count++;
} else {
intf->type = INTF_NONE;
intf->controller_id = none_count;
none_count++;
}
}
end:
return rc;
}
static int sde_wb_parse_dt(struct device_node *np, struct sde_mdss_cfg *sde_cfg)
{
int rc, prop_count[MAX_BLOCKS], i;
u32 prop_value[MAX_BLOCKS][MAX_SDE_HW_BLK] = { { 0 } };
u32 bit_value[MAX_BLOCKS][MAX_SDE_HW_BLK][MAX_BIT_OFFSET]
= { { { 0 } } };
u32 off_count;
struct sde_wb_cfg *wb;
struct sde_wb_sub_blocks *sblk;
if (!sde_cfg) {
SDE_ERROR("invalid argument\n");
rc = -EINVAL;
goto end;
}
rc = _validate_dt_entry(np, wb_prop, ARRAY_SIZE(wb_prop), prop_count,
&off_count);
if (rc)
goto end;
sde_cfg->wb_count = off_count;
rc = _read_dt_entry(np, wb_prop, ARRAY_SIZE(wb_prop), prop_count,
prop_value, bit_value);
if (rc)
goto end;
for (i = 0; i < off_count; i++) {
wb = sde_cfg->wb + i;
sblk = kzalloc(sizeof(*sblk), GFP_KERNEL);
if (!sblk) {
rc = -ENOMEM;
/* catalog deinit will release the allocated blocks */
goto end;
}
wb->sblk = sblk;
wb->base = prop_value[WB_OFF][i];
wb->id = WB_0 + i;
wb->clk_ctrl = SDE_CLK_CTRL_NONE;
wb->xin_id = prop_value[WB_XIN_ID][i];
wb->vbif_idx = VBIF_NRT;
wb->len = prop_value[WB_LEN][0];
wb->format_list = wb2_formats;
if (!wb->len)
wb->len = DEFAULT_SDE_HW_BLOCK_LEN;
sblk->maxlinewidth = sde_cfg->max_wb_linewidth;
if (i >= LINE_MODE_WB_OFFSET)
set_bit(SDE_WB_LINE_MODE, &wb->features);
else
set_bit(SDE_WB_BLOCK_MODE, &wb->features);
set_bit(SDE_WB_TRAFFIC_SHAPER, &wb->features);
set_bit(SDE_WB_YUV_CONFIG, &wb->features);
}
end:
return rc;
}
static int sde_dspp_parse_dt(struct device_node *np,
struct sde_mdss_cfg *sde_cfg)
{
int rc, prop_count[MAX_BLOCKS], i;
u32 prop_value[MAX_BLOCKS][MAX_SDE_HW_BLK] = { { 0 } };
u32 bit_value[MAX_BLOCKS][MAX_SDE_HW_BLK][MAX_BIT_OFFSET]
= { { { 0 } } };
u32 off_count;
struct sde_dspp_cfg *dspp;
struct sde_dspp_sub_blks *sblk;
if (!sde_cfg) {
SDE_ERROR("invalid argument\n");
rc = -EINVAL;
goto end;
}
rc = _validate_dt_entry(np, dspp_prop, ARRAY_SIZE(dspp_prop),
prop_count, &off_count);
if (rc)
goto end;
sde_cfg->dspp_count = off_count;
rc = _read_dt_entry(np, dspp_prop, ARRAY_SIZE(dspp_prop), prop_count,
prop_value, bit_value);
if (rc)
goto end;
for (i = 0; i < off_count; i++) {
dspp = sde_cfg->dspp + i;
dspp->base = prop_value[DSPP_OFF][i];
dspp->id = DSPP_0 + i;
sblk = kzalloc(sizeof(*sblk), GFP_KERNEL);
if (!sblk) {
rc = -ENOMEM;
/* catalog deinit will release the allocated blocks */
goto end;
}
dspp->sblk = sblk;
sblk->igc.base = prop_value[DSPP_IGC][0];
if (sblk->igc.base)
set_bit(SDE_DSPP_IGC, &dspp->features);
sblk->pcc.base = prop_value[DSPP_PCC][0];
if (sblk->pcc.base)
set_bit(SDE_DSPP_PCC, &dspp->features);
sblk->gc.base = prop_value[DSPP_GC][0];
if (sblk->gc.base)
set_bit(SDE_DSPP_GC, &dspp->features);
sblk->gamut.base = prop_value[DSPP_GAMUT][0];
if (sblk->gamut.base)
set_bit(SDE_DSPP_GAMUT, &dspp->features);
sblk->dither.base = prop_value[DSPP_DITHER][0];
if (sblk->dither.base)
set_bit(SDE_DSPP_DITHER, &dspp->features);
sblk->hist.base = prop_value[DSPP_HIST][0];
if (sblk->hist.base)
set_bit(SDE_DSPP_HIST, &dspp->features);
sblk->ad.base = prop_value[DSPP_AD][i];
if (sblk->ad.base)
set_bit(SDE_DSPP_AD, &dspp->features);
}
end:
return rc;
}
static int sde_cdm_parse_dt(struct device_node *np,
struct sde_mdss_cfg *sde_cfg)
{
int rc, prop_count[MAX_BLOCKS], i;
u32 prop_value[MAX_BLOCKS][MAX_SDE_HW_BLK] = { { 0 } };
u32 bit_value[MAX_BLOCKS][MAX_SDE_HW_BLK][MAX_BIT_OFFSET]
= { { { 0 } } };
u32 off_count;
struct sde_cdm_cfg *cdm;
if (!sde_cfg) {
SDE_ERROR("invalid argument\n");
rc = -EINVAL;
goto end;
}
rc = _validate_dt_entry(np, cdm_prop, ARRAY_SIZE(cdm_prop), prop_count,
&off_count);
if (rc)
goto end;
sde_cfg->cdm_count = off_count;
rc = _read_dt_entry(np, cdm_prop, ARRAY_SIZE(cdm_prop), prop_count,
prop_value, bit_value);
if (rc)
goto end;
for (i = 0; i < off_count; i++) {
cdm = sde_cfg->cdm + i;
cdm->base = prop_value[HW_OFF][i];
cdm->id = CDM_0 + i;
cdm->len = prop_value[HW_LEN][0];
/* intf3 and wb2 for cdm block */
cdm->wb_connect = sde_cfg->wb_count ? BIT(WB_2) : BIT(31);
cdm->intf_connect = sde_cfg->intf_count ? BIT(INTF_3) : BIT(31);
}
end:
return rc;
}
static int sde_pp_parse_dt(struct device_node *np, struct sde_mdss_cfg *sde_cfg)
{
int rc, prop_count[MAX_BLOCKS], i;
u32 prop_value[MAX_BLOCKS][MAX_SDE_HW_BLK] = { { 0 } };
u32 bit_value[MAX_BLOCKS][MAX_SDE_HW_BLK][MAX_BIT_OFFSET]
= { { { 0 } } };
u32 off_count;
struct sde_pingpong_cfg *pp;
struct sde_pingpong_sub_blks *sblk;
if (!sde_cfg) {
SDE_ERROR("invalid argument\n");
rc = -EINVAL;
goto end;
}
rc = _validate_dt_entry(np, pp_prop, ARRAY_SIZE(pp_prop), prop_count,
&off_count);
if (rc)
goto end;
sde_cfg->pingpong_count = off_count;
rc = _read_dt_entry(np, pp_prop, ARRAY_SIZE(pp_prop), prop_count,
prop_value, bit_value);
if (rc)
goto end;
for (i = 0; i < off_count; i++) {
pp = sde_cfg->pingpong + i;
sblk = kzalloc(sizeof(*sblk), GFP_KERNEL);
if (!sblk) {
rc = -ENOMEM;
/* catalog deinit will release the allocated blocks */
goto end;
}
pp->sblk = sblk;
pp->base = prop_value[PP_OFF][i];
pp->id = PINGPONG_0 + i;
pp->len = prop_value[PP_LEN][0];
sblk->te.base = prop_value[TE_OFF][i];
sblk->te.id = SDE_PINGPONG_TE;
set_bit(SDE_PINGPONG_TE, &pp->features);
sblk->te2.base = prop_value[TE2_OFF][i];
if (sblk->te2.base) {
sblk->te2.id = SDE_PINGPONG_TE2;
set_bit(SDE_PINGPONG_TE2, &pp->features);
set_bit(SDE_PINGPONG_SPLIT, &pp->features);
}
sblk->dsc.base = prop_value[DSC_OFF][i];
if (sblk->dsc.base) {
sblk->dsc.id = SDE_PINGPONG_DSC;
set_bit(SDE_PINGPONG_DSC, &pp->features);
}
}
end:
return rc;
}
static int sde_parse_dt(struct device_node *np, struct sde_mdss_cfg *cfg)
{
int rc, len, prop_count[MAX_BLOCKS];
u32 prop_value[MAX_BLOCKS][MAX_SDE_HW_BLK] = { {0} };
u32 bit_value[MAX_BLOCKS][MAX_SDE_HW_BLK][MAX_BIT_OFFSET]
= { { { 0 } } };
const char *type;
if (!cfg) {
SDE_ERROR("invalid argument\n");
rc = -EINVAL;
goto end;
}
rc = _validate_dt_entry(np, sde_prop, ARRAY_SIZE(sde_prop), prop_count,
&len);
if (rc)
goto end;
rc = _read_dt_entry(np, sde_prop, ARRAY_SIZE(sde_prop), prop_count,
prop_value, bit_value);
if (rc)
goto end;
cfg->mdss_count = 1;
cfg->mdss[0].base = MDSS_BASE_OFFSET;
cfg->mdss[0].id = MDP_TOP;
cfg->mdp_count = 1;
cfg->mdp[0].id = MDP_TOP;
cfg->mdp[0].base = prop_value[SDE_OFF][0];
cfg->mdp[0].len = prop_value[SDE_LEN][0];
if (!cfg->mdp[0].len)
cfg->mdp[0].len = DEFAULT_SDE_HW_BLOCK_LEN;
cfg->max_sspp_linewidth = prop_value[SSPP_LINEWIDTH][0];
if (!cfg->max_sspp_linewidth)
cfg->max_sspp_linewidth = DEFAULT_SDE_LINE_WIDTH;
cfg->max_mixer_width = prop_value[MIXER_LINEWIDTH][0];
if (!cfg->max_mixer_width)
cfg->max_mixer_width = DEFAULT_SDE_LINE_WIDTH;
cfg->max_mixer_blendstages = prop_value[MIXER_BLEND][0];
if (!cfg->max_mixer_blendstages)
cfg->max_mixer_blendstages = DEFAULT_SDE_MIXER_BLENDSTAGES;
cfg->max_wb_linewidth = prop_value[WB_LINEWIDTH][0];
if (!cfg->max_wb_linewidth)
cfg->max_wb_linewidth = DEFAULT_SDE_LINE_WIDTH;
cfg->mdp[0].highest_bank_bit = prop_value[BANK_BIT][0];
if (!cfg->mdp[0].highest_bank_bit)
cfg->mdp[0].highest_bank_bit = DEFAULT_SDE_HIGHEST_BANK_BIT;
rc = of_property_read_string(np, sde_prop[QSEED_TYPE].prop_name, &type);
if (!rc && !strcmp(type, "qseedv3"))
cfg->qseed_type = SDE_SSPP_SCALER_QSEED3;
else if (!rc && !strcmp(type, "qseedv2"))
cfg->qseed_type = SDE_SSPP_SCALER_QSEED2;
end:
return rc;
}
void sde_hw_catalog_deinit(struct sde_mdss_cfg *sde_cfg)
{
int i;
if (!sde_cfg)
return;
for (i = 0; i < sde_cfg->sspp_count; i++)
kfree(sde_cfg->sspp[i].sblk);
for (i = 0; i < sde_cfg->mixer_count; i++)
kfree(sde_cfg->mixer[i].sblk);
for (i = 0; i < sde_cfg->wb_count; i++)
kfree(sde_cfg->wb[i].sblk);
for (i = 0; i < sde_cfg->dspp_count; i++)
kfree(sde_cfg->dspp[i].sblk);
for (i = 0; i < sde_cfg->pingpong_count; i++)
kfree(sde_cfg->pingpong[i].sblk);
}
/*************************************************************
* hardware catalog init
*************************************************************/
struct sde_mdss_cfg *sde_hw_catalog_init(struct drm_device *dev, u32 hw_rev)
{
int rc;
struct sde_mdss_cfg *sde_cfg;
struct device_node *np = dev->dev->of_node;
sde_cfg = kzalloc(sizeof(*sde_cfg), GFP_KERNEL);
if (!sde_cfg)
return ERR_PTR(-ENOMEM);
sde_cfg->hwversion = hw_rev;
rc = sde_parse_dt(np, sde_cfg);
if (rc)
goto end;
rc = sde_ctl_parse_dt(np, sde_cfg);
if (rc)
goto end;
rc = sde_sspp_parse_dt(np, sde_cfg);
if (rc)
goto end;
rc = sde_dspp_parse_dt(np, sde_cfg);
if (rc)
goto end;
rc = sde_pp_parse_dt(np, sde_cfg);
if (rc)
goto end;
/* mixer parsing should be done after dspp and pp for mapping setup */
rc = sde_mixer_parse_dt(np, sde_cfg);
if (rc)
goto end;
rc = sde_intf_parse_dt(np, sde_cfg);
if (rc)
goto end;
rc = sde_wb_parse_dt(np, sde_cfg);
if (rc)
goto end;
/* cdm parsing should be done after intf and wb for mapping setup */
rc = sde_cdm_parse_dt(np, sde_cfg);
if (rc)
goto end;
return sde_cfg;
end:
sde_hw_catalog_deinit(sde_cfg);
kfree(sde_cfg);
return NULL;
}