blob: 5f25b2d41397e62d4f47d2e4f02f6d6d2215976c [file] [log] [blame]
/*
* Copyright (c) 2012-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-dp] %s: " fmt, __func__
#include "dp_panel.h"
#define DP_PANEL_DEFAULT_BPP 24
enum {
DP_LINK_RATE_MULTIPLIER = 27000000,
};
struct dp_panel_private {
struct device *dev;
struct dp_panel dp_panel;
struct dp_aux *aux;
struct dp_catalog_panel *catalog;
bool lane_switch_supported;
};
static int dp_panel_read_dpcd(struct dp_panel *dp_panel)
{
int rlen, rc = 0;
struct dp_panel_private *panel;
struct drm_dp_link *link_info;
u8 major = 0, minor = 0;
unsigned long caps = DP_LINK_CAP_ENHANCED_FRAMING;
if (!dp_panel) {
pr_err("invalid input\n");
rc = -EINVAL;
goto end;
}
panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
link_info = &dp_panel->link_info;
rlen = drm_dp_dpcd_read(panel->aux->drm_aux, DP_DPCD_REV,
dp_panel->dpcd, (DP_RECEIVER_CAP_SIZE + 1));
if (rlen < (DP_RECEIVER_CAP_SIZE + 1)) {
pr_err("dpcd read failed, rlen=%d\n", rlen);
rc = -EINVAL;
goto end;
}
link_info->revision = dp_panel->dpcd[DP_DPCD_REV];
major = (link_info->revision >> 4) & 0x0f;
minor = link_info->revision & 0x0f;
pr_debug("version: %d.%d\n", major, minor);
link_info->rate =
drm_dp_bw_code_to_link_rate(dp_panel->dpcd[DP_MAX_LINK_RATE]);
pr_debug("link_rate=%d\n", link_info->rate);
if (panel->lane_switch_supported)
link_info->num_lanes = dp_panel->dpcd[DP_MAX_LANE_COUNT] &
DP_MAX_LANE_COUNT_MASK;
else
link_info->num_lanes = 2;
pr_debug("lane_count=%d\n", link_info->num_lanes);
if (dp_panel->dpcd[DP_MAX_LANE_COUNT] & DP_ENHANCED_FRAME_CAP)
link_info->capabilities |= caps;
end:
return rc;
}
static u32 dp_panel_get_max_pclk(struct dp_panel *dp_panel)
{
struct drm_dp_link *link_info;
const u8 num_components = 3;
u32 bpc, bpp, max_data_rate_khz, max_pclk_rate_khz;
if (!dp_panel) {
pr_err("invalid input\n");
return 0;
}
link_info = &dp_panel->link_info;
bpc = sde_get_sink_bpc(dp_panel->edid_ctrl);
bpp = bpc * num_components;
if (!bpp)
bpp = DP_PANEL_DEFAULT_BPP;
max_data_rate_khz = (link_info->num_lanes * link_info->rate * 8);
max_pclk_rate_khz = max_data_rate_khz / bpp;
pr_debug("bpp=%d, max_lane_cnt=%d\n", bpp, link_info->num_lanes);
pr_debug("max_data_rate=%dKHz, max_pclk_rate=%dKHz\n",
max_data_rate_khz, max_pclk_rate_khz);
return max_pclk_rate_khz;
}
static int dp_panel_timing_cfg(struct dp_panel *dp_panel)
{
int rc = 0;
u32 data, total_ver, total_hor;
struct dp_catalog_panel *catalog;
struct dp_panel_private *panel;
struct dp_panel_info *pinfo;
if (!dp_panel) {
pr_err("invalid input\n");
rc = -EINVAL;
goto end;
}
panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
catalog = panel->catalog;
pinfo = &panel->dp_panel.pinfo;
pr_debug("width=%d hporch= %d %d %d\n",
pinfo->h_active, pinfo->h_back_porch,
pinfo->h_front_porch, pinfo->h_sync_width);
pr_debug("height=%d vporch= %d %d %d\n",
pinfo->v_active, pinfo->v_back_porch,
pinfo->v_front_porch, pinfo->v_sync_width);
total_hor = pinfo->h_active + pinfo->h_back_porch +
pinfo->h_front_porch + pinfo->h_sync_width;
total_ver = pinfo->v_active + pinfo->v_back_porch +
pinfo->v_front_porch + pinfo->v_sync_width;
data = total_ver;
data <<= 16;
data |= total_hor;
catalog->total = data;
data = (pinfo->v_back_porch + pinfo->v_sync_width);
data <<= 16;
data |= (pinfo->h_back_porch + pinfo->h_sync_width);
catalog->sync_start = data;
data = pinfo->v_sync_width;
data <<= 16;
data |= (pinfo->v_active_low << 31);
data |= pinfo->h_sync_width;
data |= (pinfo->h_active_low << 15);
catalog->width_blanking = data;
data = pinfo->v_active;
data <<= 16;
data |= pinfo->h_active;
catalog->dp_active = data;
panel->catalog->timing_cfg(catalog);
end:
return rc;
}
static int dp_panel_edid_register(struct dp_panel *dp_panel)
{
int rc = 0;
if (!dp_panel) {
pr_err("invalid input\n");
rc = -EINVAL;
goto end;
}
dp_panel->edid_ctrl = sde_edid_init();
if (!dp_panel->edid_ctrl) {
pr_err("sde edid init for DP failed\n");
rc = -ENOMEM;
goto end;
}
end:
return rc;
}
static void dp_panel_edid_deregister(struct dp_panel *dp_panel)
{
if (!dp_panel) {
pr_err("invalid input\n");
return;
}
sde_edid_deinit((void **)&dp_panel->edid_ctrl);
}
static int dp_panel_init_panel_info(struct dp_panel *dp_panel)
{
int rc = 0;
struct dp_panel_info *pinfo;
if (!dp_panel) {
pr_err("invalid input\n");
rc = -EINVAL;
goto end;
}
pinfo = &dp_panel->pinfo;
/*
* print resolution info as this is a result
* of user initiated action of cable connection
*/
pr_info("SET NEW RESOLUTION:\n");
pr_info("%dx%d@%dfps\n", pinfo->h_active,
pinfo->v_active, pinfo->refresh_rate);
pr_info("h_porches(back|front|width) = (%d|%d|%d)\n",
pinfo->h_back_porch,
pinfo->h_front_porch,
pinfo->h_sync_width);
pr_info("v_porches(back|front|width) = (%d|%d|%d)\n",
pinfo->v_back_porch,
pinfo->v_front_porch,
pinfo->v_sync_width);
pr_info("pixel clock (KHz)=(%d)\n", pinfo->pixel_clk_khz);
pr_info("bpp = %d\n", pinfo->bpp);
pr_info("active low (h|v)=(%d|%d)\n", pinfo->h_active_low,
pinfo->v_active_low);
pinfo->bpp = max_t(u32, 18, min_t(u32, pinfo->bpp, 30));
pr_info("updated bpp = %d\n", pinfo->bpp);
end:
return rc;
}
static u32 dp_panel_get_min_req_link_rate(struct dp_panel *dp_panel)
{
const u32 encoding_factx10 = 8;
u32 min_link_rate_khz = 0, lane_cnt;
struct dp_panel_info *pinfo;
if (!dp_panel) {
pr_err("invalid input\n");
goto end;
}
lane_cnt = dp_panel->link_info.num_lanes;
pinfo = &dp_panel->pinfo;
/* num_lanes * lane_count * 8 >= pclk * bpp * 10 */
min_link_rate_khz = pinfo->pixel_clk_khz /
(lane_cnt * encoding_factx10);
min_link_rate_khz *= pinfo->bpp;
pr_debug("min lclk req=%d khz for pclk=%d khz, lanes=%d, bpp=%d\n",
min_link_rate_khz, pinfo->pixel_clk_khz, lane_cnt,
pinfo->bpp);
end:
return min_link_rate_khz;
}
struct dp_panel *dp_panel_get(struct device *dev, struct dp_aux *aux,
struct dp_catalog_panel *catalog)
{
int rc = 0;
struct dp_panel_private *panel;
struct dp_panel *dp_panel;
if (!dev || !aux || !catalog) {
pr_err("invalid input\n");
rc = -EINVAL;
goto error;
}
panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
if (!panel) {
rc = -ENOMEM;
goto error;
}
panel->dev = dev;
panel->aux = aux;
panel->catalog = catalog;
dp_panel = &panel->dp_panel;
dp_panel->sde_edid_register = dp_panel_edid_register;
dp_panel->sde_edid_deregister = dp_panel_edid_deregister;
dp_panel->init_info = dp_panel_init_panel_info;
dp_panel->timing_cfg = dp_panel_timing_cfg;
dp_panel->read_dpcd = dp_panel_read_dpcd;
dp_panel->get_min_req_link_rate = dp_panel_get_min_req_link_rate;
dp_panel->get_max_pclk = dp_panel_get_max_pclk;
return dp_panel;
error:
return ERR_PTR(rc);
}
void dp_panel_put(struct dp_panel *dp_panel)
{
struct dp_panel_private *panel;
if (!dp_panel)
return;
panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
devm_kfree(panel->dev, panel);
}