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_ */