diff mbox

[5/7] drm/i915: Add and register lspcon connector functions

Message ID 1458656708-31228-6-git-send-email-shashank.sharma@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Sharma, Shashank March 22, 2016, 2:25 p.m. UTC
This patch adds various lspcon connector functions. Some
of the functions are newly written, to meet the specific
needs of lspcon HW, whereas few of them are just an
abstraction layer on existing HDMI connector functions.

Signed-off-by: Shashank Sharma <shashank.sharma@intel.com>
---
 drivers/gpu/drm/i915/intel_drv.h     |  11 +-
 drivers/gpu/drm/i915/intel_hdmi.c    |   8 +-
 drivers/gpu/drm/i915/intel_hotplug.c |   2 +-
 drivers/gpu/drm/i915/intel_lspcon.c  | 238 ++++++++++++++++++++++++++++++++++-
 4 files changed, 251 insertions(+), 8 deletions(-)
diff mbox

Patch

diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 09273d5..7b19a2c 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -1403,7 +1403,12 @@  int intel_hdmi_init_minimum(struct intel_digital_port *intel_dig_port,
 struct intel_hdmi *enc_to_intel_hdmi(struct drm_encoder *encoder);
 bool intel_hdmi_compute_config(struct intel_encoder *encoder,
 			       struct intel_crtc_state *pipe_config);
-
+int intel_hdmi_get_modes(struct drm_connector *connector);
+int intel_hdmi_set_property(struct drm_connector *connector,
+		struct drm_property *property, uint64_t val);
+void intel_hdmi_destroy(struct drm_connector *connector);
+void intel_hdmi_add_properties(struct intel_hdmi *intel_hdmi,
+		struct drm_connector *connector);
 
 /* intel_lvds.c */
 void intel_lvds_init(struct drm_device *dev);
@@ -1492,6 +1497,10 @@  bool intel_display_power_get_if_enabled(struct drm_i915_private *dev_priv,
 void intel_display_power_put(struct drm_i915_private *dev_priv,
 			     enum intel_display_power_domain domain);
 
+/* intel_hotplug.c */
+bool intel_hpd_irq_event(struct drm_device *dev,
+		struct drm_connector *connector);
+
 static inline void
 assert_rpm_device_not_suspended(struct drm_i915_private *dev_priv)
 {
diff --git a/drivers/gpu/drm/i915/intel_hdmi.c b/drivers/gpu/drm/i915/intel_hdmi.c
index 9fcbbdf..b67bb30 100644
--- a/drivers/gpu/drm/i915/intel_hdmi.c
+++ b/drivers/gpu/drm/i915/intel_hdmi.c
@@ -1447,7 +1447,7 @@  intel_hdmi_force(struct drm_connector *connector)
 	hdmi_to_dig_port(intel_hdmi)->base.type = INTEL_OUTPUT_HDMI;
 }
 
-static int intel_hdmi_get_modes(struct drm_connector *connector)
+int intel_hdmi_get_modes(struct drm_connector *connector)
 {
 	struct edid *edid;
 
@@ -1471,7 +1471,7 @@  intel_hdmi_detect_audio(struct drm_connector *connector)
 	return has_audio;
 }
 
-static int
+int
 intel_hdmi_set_property(struct drm_connector *connector,
 			struct drm_property *property,
 			uint64_t val)
@@ -1996,7 +1996,7 @@  static void chv_hdmi_pre_enable(struct intel_encoder *encoder)
 	}
 }
 
-static void intel_hdmi_destroy(struct drm_connector *connector)
+void intel_hdmi_destroy(struct drm_connector *connector)
 {
 	kfree(to_intel_connector(connector)->detect_edid);
 	drm_connector_cleanup(connector);
@@ -2025,7 +2025,7 @@  static const struct drm_encoder_funcs intel_hdmi_enc_funcs = {
 	.destroy = intel_encoder_destroy,
 };
 
-static void
+void
 intel_hdmi_add_properties(struct intel_hdmi *intel_hdmi, struct drm_connector *connector)
 {
 	intel_attach_force_audio_property(connector);
diff --git a/drivers/gpu/drm/i915/intel_hotplug.c b/drivers/gpu/drm/i915/intel_hotplug.c
index bee6730..11a3e02 100644
--- a/drivers/gpu/drm/i915/intel_hotplug.c
+++ b/drivers/gpu/drm/i915/intel_hotplug.c
@@ -226,7 +226,7 @@  static void intel_hpd_irq_storm_reenable_work(struct work_struct *work)
 	intel_runtime_pm_put(dev_priv);
 }
 
-static bool intel_hpd_irq_event(struct drm_device *dev,
+bool intel_hpd_irq_event(struct drm_device *dev,
 				struct drm_connector *connector)
 {
 	enum drm_connector_status old_status;
diff --git a/drivers/gpu/drm/i915/intel_lspcon.c b/drivers/gpu/drm/i915/intel_lspcon.c
index 9d5ed0c..e64abd3 100644
--- a/drivers/gpu/drm/i915/intel_lspcon.c
+++ b/drivers/gpu/drm/i915/intel_lspcon.c
@@ -39,6 +39,8 @@ 
 #define DP_TYPE2_ADAPTER			0xA0
 #define ADAPTER_TYPE_MASK			0xF0
 #define LSPCON_MODE_MASK			0x1
+#define DDC_SEGMENT_ADDR			0x30
+#define DDC_ADDR				0x50
 
 struct intel_digital_port *lspcon_to_dig_port(struct intel_lspcon *lspcon)
 {
@@ -57,6 +59,11 @@  struct intel_lspcon *enc_to_lspcon(struct drm_encoder *encoder)
 	return &intel_dig_port->lspcon;
 }
 
+struct intel_lspcon *intel_attached_lspcon(struct drm_connector *connector)
+{
+	return enc_to_lspcon(&intel_attached_encoder(connector)->base);
+}
+
 int lspcon_ioa_read(struct i2c_adapter *adapter, u8 *buffer,
 		u8 address, u8 offset, u8 no_of_bytes)
 {
@@ -113,6 +120,214 @@  int lspcon_ioa_write(struct i2c_adapter *adapter, u8 *buffer,
 	return err;
 }
 
+static int lspcon_get_edid_over_aux(void *data,
+	u8 *buf, unsigned int block, size_t len)
+{
+	struct i2c_adapter *adapter = data;
+	unsigned char start = block * EDID_LENGTH;
+	unsigned char segment = block >> 1;
+	unsigned char xfers = segment ? 3 : 2;
+	int ret, retries = 5;
+
+	do {
+		struct i2c_msg msgs[] = {
+			{
+				.addr   = DDC_SEGMENT_ADDR,
+				.flags  = 0,
+				.len    = 1,
+				.buf    = &segment,
+			}, {
+				.addr   = DDC_ADDR,
+				.flags  = 0,
+				.len    = 1,
+				.buf    = &start,
+			}, {
+				.addr   = DDC_ADDR,
+				.flags  = I2C_M_RD,
+				.len    = len,
+				.buf    = buf,
+			}
+		};
+
+		ret = adapter->algo->master_xfer(adapter, &msgs[3 - xfers],
+						xfers);
+
+		if (ret == -ENXIO) {
+			DRM_ERROR("Non-existent adapter %s\n",
+				adapter->name);
+			break;
+		}
+	} while (ret != xfers && --retries);
+
+	return ret == xfers ? 0 : -1;
+}
+
+struct edid *lspcon_get_edid(struct intel_lspcon *lspcon, struct drm_connector
+						*connector)
+{
+	struct edid *edid = NULL;
+	struct intel_digital_port *dig_port = lspcon_to_dig_port(lspcon);
+	struct i2c_adapter *adapter = &dig_port->dp.aux.ddc;
+
+	if (lspcon->mode_of_op != lspcon_mode_ls) {
+		DRM_ERROR("Cant read EDID without current mode info\n");
+		return false;
+	}
+
+	/* LS mode, getting EDID using I2C over Aux */
+	edid = drm_do_get_edid(connector, lspcon_get_edid_over_aux,
+			(void *)adapter);
+	return edid;
+}
+
+static void
+lspcon_unset_edid(struct drm_connector *connector)
+{
+	struct intel_lspcon *lspcon = intel_attached_lspcon(connector);
+	struct intel_hdmi *intel_hdmi = lspcon_to_hdmi(lspcon);
+
+	intel_hdmi->has_hdmi_sink = false;
+	intel_hdmi->has_audio = false;
+	intel_hdmi->rgb_quant_range_selectable = false;
+
+	kfree(to_intel_connector(connector)->detect_edid);
+	to_intel_connector(connector)->detect_edid = NULL;
+}
+
+static bool
+lspcon_set_edid(struct drm_connector *connector, bool force)
+{
+	struct drm_i915_private *dev_priv = to_i915(connector->dev);
+	struct intel_lspcon *lspcon = intel_attached_lspcon(connector);
+	struct intel_hdmi *intel_hdmi = lspcon_to_hdmi(lspcon);
+	struct edid *edid = NULL;
+	bool connected = false;
+
+	if (force) {
+		intel_display_power_get(dev_priv, POWER_DOMAIN_GMBUS);
+		edid = lspcon_get_edid(lspcon, connector);
+		intel_display_power_put(dev_priv, POWER_DOMAIN_GMBUS);
+	}
+
+	to_intel_connector(connector)->detect_edid = edid;
+	if (edid && edid->input & DRM_EDID_INPUT_DIGITAL) {
+		intel_hdmi->rgb_quant_range_selectable =
+			drm_rgb_quant_range_selectable(edid);
+
+		intel_hdmi->has_audio = drm_detect_monitor_audio(edid);
+		if (intel_hdmi->force_audio != HDMI_AUDIO_AUTO)
+			intel_hdmi->has_audio =
+				intel_hdmi->force_audio == HDMI_AUDIO_ON;
+
+		if (intel_hdmi->force_audio != HDMI_AUDIO_OFF_DVI)
+			intel_hdmi->has_hdmi_sink =
+				drm_detect_hdmi_monitor(edid);
+
+		connected = true;
+	}
+	return connected;
+}
+
+static enum drm_connector_status
+lspcon_detect(struct drm_connector *connector, bool force)
+{
+	enum drm_connector_status status;
+	struct drm_i915_private *dev_priv = to_i915(connector->dev);
+	struct intel_lspcon *lspcon = intel_attached_lspcon(connector);
+	struct intel_hdmi *intel_hdmi = lspcon_to_hdmi(lspcon);
+
+	DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n",
+		      connector->base.id, connector->name);
+	intel_display_power_get(dev_priv, POWER_DOMAIN_GMBUS);
+
+	lspcon_unset_edid(connector);
+	if (lspcon_set_edid(connector, true)) {
+		DRM_DEBUG_DRIVER("HDMI connected\n");
+		hdmi_to_dig_port(intel_hdmi)->base.type = INTEL_OUTPUT_HDMI;
+		status = connector_status_connected;
+	} else {
+		DRM_DEBUG_DRIVER("HDMI disconnected\n");
+		status = connector_status_disconnected;
+	}
+	intel_display_power_put(dev_priv, POWER_DOMAIN_GMBUS);
+	return status;
+}
+
+static int
+lspcon_set_property(struct drm_connector *connector,
+			struct drm_property *property,
+			uint64_t val)
+{
+	return intel_hdmi_set_property(connector, property, val);
+}
+
+static int
+lspcon_get_modes(struct drm_connector *connector)
+{
+	return intel_hdmi_get_modes(connector);
+}
+
+static void
+lspcon_destroy(struct drm_connector *connector)
+{
+	intel_hdmi_destroy(connector);
+}
+
+static enum drm_mode_status
+lspcon_mode_valid(struct drm_connector *connector,
+		      struct drm_display_mode *mode)
+{
+	int clock = mode->clock;
+	int max_dotclk = 675000; /* 4k@60 */
+	struct drm_device *dev = connector->dev;
+
+	if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
+		return MODE_NO_DBLESCAN;
+
+	if ((mode->flags & DRM_MODE_FLAG_3D_MASK) ==
+		DRM_MODE_FLAG_3D_FRAME_PACKING)
+		clock *= 2;
+
+	if (mode->flags & DRM_MODE_FLAG_DBLCLK)
+		clock *= 2;
+
+	if (clock < 25000)
+		return MODE_CLOCK_LOW;
+
+	if (clock > max_dotclk)
+		return MODE_CLOCK_HIGH;
+
+	/* BXT DPLL can't generate 223-240 MHz */
+	if (IS_BROXTON(dev) && clock > 223333 && clock < 240000)
+		return MODE_CLOCK_RANGE;
+
+	/* todo: check for 12bpc here */
+	return MODE_OK;
+}
+
+void lspcon_add_properties(struct intel_digital_port *dig_port,
+		struct drm_connector *connector)
+{
+	intel_hdmi_add_properties(&dig_port->hdmi, connector);
+}
+
+static const struct drm_connector_funcs lspcon_connector_funcs = {
+	.dpms = drm_atomic_helper_connector_dpms,
+	.detect = lspcon_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.set_property = lspcon_set_property,
+	.atomic_get_property = intel_connector_atomic_get_property,
+	.destroy = lspcon_destroy,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+};
+
+static const struct drm_connector_helper_funcs lspcon_connector_helper_funcs = {
+	.get_modes = lspcon_get_modes,
+	.mode_valid = lspcon_mode_valid,
+	.best_encoder = intel_best_encoder,
+};
+
 void intel_lspcon_init_connector(struct intel_digital_port *intel_dig_port)
 {
 	struct intel_encoder *intel_encoder = &intel_dig_port->base;
@@ -130,16 +345,28 @@  void intel_lspcon_init_connector(struct intel_digital_port *intel_dig_port)
 	connector->interlace_allowed = true;
 	connector->doublescan_allowed = 0;
 
+	/* Load connector */
+	drm_connector_init(dev, connector, &lspcon_connector_funcs,
+			DRM_MODE_CONNECTOR_DisplayPort);
+	drm_connector_helper_add(connector, &lspcon_connector_helper_funcs);
+	intel_connector_attach_encoder(intel_connector, intel_encoder);
+	drm_connector_register(connector);
+
+	/* Add properties and functions */
+	lspcon_add_properties(intel_dig_port, connector);
+	intel_connector->get_hw_state = intel_ddi_connector_get_hw_state;
+	i915_debugfs_connector_add(connector);
+
 	/* init DP */
 	if (intel_dp_init_minimum(intel_dig_port, intel_connector)) {
 		DRM_ERROR("DP init for LSPCON failed\n");
-		return;
+		goto fail;
 	}
 
 	/* init HDMI */
 	if (intel_hdmi_init_minimum(intel_dig_port, intel_connector)) {
 		DRM_ERROR("HDMI init for LSPCON failed\n");
-		return;
+		goto fail;
 	}
 
 	/* Set up the hotplug pin. */
@@ -183,4 +410,11 @@  void intel_lspcon_init_connector(struct intel_digital_port *intel_dig_port)
 
 		I915_WRITE(PEG_BAND_GAP_DATA, (temp & ~0xf) | 0xd);
 	}
+
+	DRM_DEBUG_DRIVER("LSPCON connector init done\n");
+	return;
+
+fail:
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
 }