drm/msm/dp: add support for dp panel
Add DisplayPort panel module to handle the panel specific
functionalities like DPCD read, EDID read, managing resolution
timings and hardware programming.
Change-Id: I7487ba415f0cc726c0380bcc385b154a6175a330
Signed-off-by: Ajay Singh Parmar <aparmar@codeaurora.org>
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
new file mode 100644
index 0000000..f9616c4
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -0,0 +1,468 @@
+/*
+ * 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_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;
+};
+
+static int dp_panel_read_dpcd(struct dp_panel *dp_panel)
+{
+ u8 *bp;
+ u8 data;
+ u32 const addr = 0x0;
+ u32 const len = 16;
+ int rlen, rc = 0;
+ struct dp_panel_private *panel;
+ struct dp_panel_dpcd *cap;
+
+ if (!dp_panel) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto end;
+ }
+
+ cap = &dp_panel->dpcd;
+ panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+
+ rlen = panel->aux->read(panel->aux, addr, len, AUX_NATIVE, &bp);
+ if (rlen != len) {
+ pr_err("dpcd read failed, rlen=%d\n", rlen);
+ rc = -EINVAL;
+ goto end;
+ }
+
+ memset(cap, 0, sizeof(*cap));
+
+ data = *bp++; /* byte 0 */
+ cap->major = (data >> 4) & 0x0f;
+ cap->minor = data & 0x0f;
+ pr_debug("version: %d.%d\n", cap->major, cap->minor);
+
+ data = *bp++; /* byte 1 */
+ /* 162, 270, 540, 810 MB, symbol rate, NOT bit rate */
+ cap->max_link_rate = data;
+ pr_debug("link_rate=%d\n", cap->max_link_rate);
+
+ data = *bp++; /* byte 2 */
+ if (data & BIT(7))
+ cap->enhanced_frame++;
+
+ if (data & 0x40) {
+ cap->flags |= DPCD_TPS3;
+ pr_debug("pattern 3 supported\n");
+ } else {
+ pr_debug("pattern 3 not supported\n");
+ }
+
+ data &= 0x0f;
+ cap->max_lane_count = data;
+ pr_debug("lane_count=%d\n", cap->max_lane_count);
+
+ data = *bp++; /* byte 3 */
+ if (data & BIT(0)) {
+ cap->flags |= DPCD_MAX_DOWNSPREAD_0_5;
+ pr_debug("max_downspread\n");
+ }
+
+ if (data & BIT(6)) {
+ cap->flags |= DPCD_NO_AUX_HANDSHAKE;
+ pr_debug("NO Link Training\n");
+ }
+
+ data = *bp++; /* byte 4 */
+ cap->num_rx_port = (data & BIT(0)) + 1;
+ pr_debug("rx_ports=%d", cap->num_rx_port);
+
+ data = *bp++; /* Byte 5: DOWN_STREAM_PORT_PRESENT */
+ cap->downstream_port.dfp_present = data & BIT(0);
+ cap->downstream_port.dfp_type = data & 0x6;
+ cap->downstream_port.format_conversion = data & BIT(3);
+ cap->downstream_port.detailed_cap_info_available = data & BIT(4);
+ pr_debug("dfp_present = %d, dfp_type = %d\n",
+ cap->downstream_port.dfp_present,
+ cap->downstream_port.dfp_type);
+ pr_debug("format_conversion = %d, detailed_cap_info_available = %d\n",
+ cap->downstream_port.format_conversion,
+ cap->downstream_port.detailed_cap_info_available);
+
+ bp += 1; /* Skip Byte 6 */
+ rlen -= 1;
+
+ data = *bp++; /* Byte 7: DOWN_STREAM_PORT_COUNT */
+ cap->downstream_port.dfp_count = data & 0x7;
+ cap->downstream_port.msa_timing_par_ignored = data & BIT(6);
+ cap->downstream_port.oui_support = data & BIT(7);
+ pr_debug("dfp_count = %d, msa_timing_par_ignored = %d\n",
+ cap->downstream_port.dfp_count,
+ cap->downstream_port.msa_timing_par_ignored);
+ pr_debug("oui_support = %d\n", cap->downstream_port.oui_support);
+
+ data = *bp++; /* byte 8 */
+ if (data & BIT(1)) {
+ cap->flags |= DPCD_PORT_0_EDID_PRESENTED;
+ pr_debug("edid presented\n");
+ }
+
+ data = *bp++; /* byte 9 */
+ cap->rx_port0_buf_size = (data + 1) * 32;
+ pr_debug("lane_buf_size=%d\n", cap->rx_port0_buf_size);
+
+ bp += 2; /* skip 10, 11 port1 capability */
+ rlen -= 2;
+
+ data = *bp++; /* byte 12 */
+ cap->i2c_speed_ctrl = data;
+ if (cap->i2c_speed_ctrl > 0)
+ pr_debug("i2c_rate=%d", cap->i2c_speed_ctrl);
+
+ data = *bp++; /* byte 13 */
+ cap->scrambler_reset = data & BIT(0);
+ pr_debug("scrambler_reset=%d\n", cap->scrambler_reset);
+
+ if (data & BIT(1))
+ cap->enhanced_frame++;
+
+ pr_debug("enhanced_framing=%d\n", cap->enhanced_frame);
+
+ data = *bp++; /* byte 14 */
+ if (data == 0)
+ cap->training_read_interval = 4000; /* us */
+ else
+ cap->training_read_interval = 4000 * data; /* us */
+ pr_debug("training_interval=%d\n", cap->training_read_interval);
+end:
+ return rc;
+}
+
+/*
+ * edid standard header bytes
+ */
+static u8 edid_hdr[8] = {0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00};
+
+static bool dp_panel_is_edid_header_valid(u8 *buf)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(edid_hdr); i++) {
+ if (buf[i] != edid_hdr[i])
+ return false;
+ }
+
+ return true;
+}
+
+static int dp_panel_validate_edid(u8 *bp, int len)
+{
+ int i;
+ u8 csum = 0;
+ u32 const size = 128;
+
+ if (len < size) {
+ pr_err("Error: len=%x\n", len);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < size; i++)
+ csum += *bp++;
+
+ if (csum != 0) {
+ pr_err("error: csum=0x%x\n", csum);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int dp_panel_read_edid(struct dp_panel *dp_panel)
+{
+ u8 *edid_buf;
+ u32 checksum = 0;
+ int rlen, ret = 0;
+ int edid_blk = 0, blk_num = 0, retries = 10;
+ u32 const segment_addr = 0x30;
+ bool edid_parsing_done = false;
+ struct dp_panel_private *panel;
+
+ panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+
+ ret = panel->aux->ready(panel->aux);
+ if (!ret) {
+ pr_err("aux chan NOT ready\n");
+ goto end;
+ }
+
+ do {
+ u8 segment;
+
+
+ /*
+ * Write the segment first.
+ * Segment = 0, for blocks 0 and 1
+ * Segment = 1, for blocks 2 and 3
+ * Segment = 2, for blocks 3 and 4
+ * and so on ...
+ */
+ segment = blk_num >> 1;
+
+ panel->aux->write(panel->aux, segment_addr, 1, AUX_I2C,
+ &segment);
+
+ rlen = panel->aux->read(panel->aux, EDID_START_ADDRESS +
+ (blk_num * EDID_BLOCK_SIZE),
+ EDID_BLOCK_SIZE, AUX_I2C, &edid_buf);
+ if (rlen != EDID_BLOCK_SIZE) {
+ pr_err("invalid edid len: %d\n", rlen);
+ continue;
+ }
+
+ pr_debug("=== EDID data ===\n");
+ print_hex_dump(KERN_DEBUG, "EDID: ", DUMP_PREFIX_NONE, 16, 1,
+ edid_buf, EDID_BLOCK_SIZE, false);
+
+ pr_debug("blk_num=%d, rlen=%d\n", blk_num, rlen);
+
+ if (dp_panel_is_edid_header_valid(edid_buf)) {
+ ret = dp_panel_validate_edid(edid_buf, rlen);
+ if (ret) {
+ pr_err("corrupt edid block detected\n");
+ goto end;
+ }
+
+ if (edid_parsing_done) {
+ blk_num++;
+ continue;
+ }
+
+ dp_panel->edid.ext_block_cnt = edid_buf[0x7E];
+ edid_parsing_done = true;
+ checksum = edid_buf[rlen - 1];
+ } else {
+ edid_blk++;
+ blk_num++;
+ }
+
+ memcpy(dp_panel->edid.buf + (edid_blk * EDID_BLOCK_SIZE),
+ edid_buf, EDID_BLOCK_SIZE);
+
+ if (edid_blk == dp_panel->edid.ext_block_cnt)
+ goto end;
+ } while (retries--);
+end:
+ return ret;
+}
+
+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_init_panel_info(struct dp_panel *dp_panel)
+{
+ int rc = 0;
+ struct dp_panel_private *panel;
+
+ if (!dp_panel) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto end;
+ }
+
+ panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+end:
+ return rc;
+}
+
+static u8 dp_panel_get_link_rate(struct dp_panel *dp_panel)
+{
+ const u32 encoding_factx10 = 8;
+ const u32 ln_to_link_ratio = 10;
+ u32 min_link_rate, reminder = 0;
+ u8 calc_link_rate = 0, lane_cnt;
+ struct dp_panel_private *panel;
+ struct dp_panel_info *pinfo;
+
+ if (!dp_panel) {
+ pr_err("invalid input\n");
+ goto end;
+ }
+
+ panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+
+ lane_cnt = dp_panel->dpcd.max_lane_count;
+ pinfo = &dp_panel->pinfo;
+
+ pinfo->bpp = 24;
+
+ /*
+ * The max pixel clock supported is 675Mhz. The
+ * current calculations below will make sure
+ * the min_link_rate is within 32 bit limits.
+ * Any changes in the section of code should
+ * consider this limitation.
+ */
+ min_link_rate = (u32)div_u64(pinfo->pixel_clk_khz * 1000,
+ (lane_cnt * encoding_factx10));
+ min_link_rate /= ln_to_link_ratio;
+ min_link_rate = (min_link_rate * pinfo->bpp);
+ min_link_rate = (u32)div_u64_rem(min_link_rate * 10,
+ DP_LINK_RATE_MULTIPLIER, &reminder);
+
+ /*
+ * To avoid any fractional values,
+ * increment the min_link_rate
+ */
+ if (reminder)
+ min_link_rate += 1;
+ pr_debug("min_link_rate = %d\n", min_link_rate);
+
+ if (min_link_rate <= DP_LINK_RATE_162)
+ calc_link_rate = DP_LINK_RATE_162;
+ else if (min_link_rate <= DP_LINK_RATE_270)
+ calc_link_rate = DP_LINK_RATE_270;
+ else if (min_link_rate <= DP_LINK_RATE_540)
+ calc_link_rate = DP_LINK_RATE_540;
+ else if (min_link_rate <= DP_LINK_RATE_810)
+ calc_link_rate = DP_LINK_RATE_810;
+ else {
+ /* Cap the link rate to the max supported rate */
+ pr_debug("link_rate = %d is unsupported\n", min_link_rate);
+ calc_link_rate = DP_LINK_RATE_810;
+ }
+
+ if (calc_link_rate > dp_panel->dpcd.max_link_rate)
+ calc_link_rate = dp_panel->dpcd.max_link_rate;
+
+ pr_debug("calc_link_rate = 0x%x\n", calc_link_rate);
+end:
+ return calc_link_rate;
+}
+
+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->edid.buf = devm_kzalloc(dev,
+ sizeof(EDID_BLOCK_SIZE) * 4, GFP_KERNEL);
+
+ dp_panel->init_info = dp_panel_init_panel_info;
+ dp_panel->timing_cfg = dp_panel_timing_cfg;
+ dp_panel->read_edid = dp_panel_read_edid;
+ dp_panel->read_dpcd = dp_panel_read_dpcd;
+ dp_panel->get_link_rate = dp_panel_get_link_rate;
+
+ 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, dp_panel->edid.buf);
+ devm_kfree(panel->dev, panel);
+}
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
new file mode 100644
index 0000000..5c145eb
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_panel.h
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef _DP_PANEL_H_
+#define _DP_PANEL_H_
+
+#include "dp_aux.h"
+
+#define DPCD_ENHANCED_FRAME BIT(0)
+#define DPCD_TPS3 BIT(1)
+#define DPCD_MAX_DOWNSPREAD_0_5 BIT(2)
+#define DPCD_NO_AUX_HANDSHAKE BIT(3)
+#define DPCD_PORT_0_EDID_PRESENTED BIT(4)
+
+#define EDID_START_ADDRESS 0x50
+#define EDID_BLOCK_SIZE 0x80
+
+
+#define DP_LINK_RATE_162 6 /* 1.62G = 270M * 6 */
+#define DP_LINK_RATE_270 10 /* 2.70G = 270M * 10 */
+#define DP_LINK_RATE_540 20 /* 5.40G = 270M * 20 */
+#define DP_LINK_RATE_810 30 /* 8.10G = 270M * 30 */
+#define DP_LINK_RATE_MAX DP_LINK_RATE_810
+
+struct downstream_port_config {
+ /* Byte 02205h */
+ bool dfp_present;
+ u32 dfp_type;
+ bool format_conversion;
+ bool detailed_cap_info_available;
+ /* Byte 02207h */
+ u32 dfp_count;
+ bool msa_timing_par_ignored;
+ bool oui_support;
+};
+
+struct dp_panel_dpcd {
+ u8 major;
+ u8 minor;
+ u8 max_lane_count;
+ u8 num_rx_port;
+ u8 i2c_speed_ctrl;
+ u8 scrambler_reset;
+ u8 enhanced_frame;
+ u32 max_link_rate; /* 162, 270 and 540 Mb, divided by 10 */
+ u32 flags;
+ u32 rx_port0_buf_size;
+ u32 training_read_interval;/* us */
+ struct downstream_port_config downstream_port;
+};
+
+struct dp_panel_edid {
+ u8 *buf;
+ u8 id_name[4];
+ u8 id_product;
+ u8 version;
+ u8 revision;
+ u8 video_intf; /* dp == 0x5 */
+ u8 color_depth; /* 6, 8, 10, 12 and 14 bits */
+ u8 color_format; /* RGB 4:4:4, YCrCb 4:4:4, Ycrcb 4:2:2 */
+ u8 dpm; /* display power management */
+ u8 sync_digital; /* 1 = digital */
+ u8 sync_separate; /* 1 = separate */
+ u8 vsync_pol; /* 0 = negative, 1 = positive */
+ u8 hsync_pol; /* 0 = negative, 1 = positive */
+ u8 ext_block_cnt;
+};
+
+struct dp_panel_info {
+ u32 h_active;
+ u32 v_active;
+ u32 h_back_porch;
+ u32 h_front_porch;
+ u32 h_sync_width;
+ u32 h_active_low;
+ u32 v_back_porch;
+ u32 v_front_porch;
+ u32 v_sync_width;
+ u32 v_active_low;
+ u32 h_skew;
+ u32 refresh_rate;
+ u32 pixel_clk_khz;
+ u32 bpp;
+};
+
+struct dp_panel {
+ struct dp_panel_dpcd dpcd;
+ struct dp_panel_edid edid;
+ struct dp_panel_info pinfo;
+
+ u32 vic;
+
+ int (*init_info)(struct dp_panel *dp_panel);
+ int (*timing_cfg)(struct dp_panel *dp_panel);
+ int (*read_edid)(struct dp_panel *dp_panel);
+ int (*read_dpcd)(struct dp_panel *dp_panel);
+ u8 (*get_link_rate)(struct dp_panel *dp_panel);
+};
+
+struct dp_panel *dp_panel_get(struct device *dev, struct dp_aux *aux,
+ struct dp_catalog_panel *catalog);
+void dp_panel_put(struct dp_panel *dp_panel);
+#endif /* _DP_PANEL_H_ */