summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/bridge/cadence/cdns-dp-core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/bridge/cadence/cdns-dp-core.c')
-rw-r--r--drivers/gpu/drm/bridge/cadence/cdns-dp-core.c881
1 files changed, 881 insertions, 0 deletions
diff --git a/drivers/gpu/drm/bridge/cadence/cdns-dp-core.c b/drivers/gpu/drm/bridge/cadence/cdns-dp-core.c
new file mode 100644
index 000000000000..0b7afd009995
--- /dev/null
+++ b/drivers/gpu/drm/bridge/cadence/cdns-dp-core.c
@@ -0,0 +1,881 @@
+/*
+ * Cadence Display Port Interface (DP) driver
+ *
+ * Copyright 2019 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ */
+#include <drm/bridge/cdns-mhdp.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/drm_hdcp.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_print.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_device.h>
+
+#include "cdns-mhdp-hdcp.h"
+#include "cdns-hdcp-common.h"
+
+#define CDNS_DP_HPD_POLL_DWN_LOOP 5
+#define CDNS_DP_HPD_POLL_DWN_DLY_US 200
+#define CDNS_DP_HPD_POLL_UP_LOOP 5
+#define CDNS_DP_HPD_POLL_UP_DLY_US 1000
+
+/*
+ * This function only implements native DPDC reads and writes
+ */
+static ssize_t dp_aux_transfer(struct drm_dp_aux *aux,
+ struct drm_dp_aux_msg *msg)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(aux->dev);
+ bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ);
+ int ret;
+
+ /* Ignore address only message , for native */
+ if ((native == true) && ((msg->size == 0) || (msg->buffer == NULL))) {
+ msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+ return msg->size;
+ }
+
+ /* msg sanity check */
+ if (msg->size > DP_AUX_MAX_PAYLOAD_BYTES) {
+ dev_err(mhdp->dev, "%s: invalid msg: size(%zu), request(%x)\n",
+ __func__, msg->size, (unsigned int)msg->request);
+ return -EINVAL;
+ }
+
+ if (msg->request == DP_AUX_NATIVE_WRITE) {
+ const u8 *buf = msg->buffer;
+ int i;
+ for (i = 0; i < msg->size; ++i) {
+ ret = cdns_mhdp_dpcd_write(mhdp,
+ msg->address + i, buf[i]);
+ if (!ret)
+ continue;
+
+ DRM_DEV_ERROR(mhdp->dev, "Failed to write DPCD\n");
+
+ return ret;
+ }
+ msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+ return msg->size;
+ }
+
+ if (msg->request == DP_AUX_NATIVE_READ) {
+ ret = cdns_mhdp_dpcd_read(mhdp, msg->address, msg->buffer,
+ msg->size);
+ if (ret < 0)
+ return -EIO;
+ msg->reply = DP_AUX_NATIVE_REPLY_ACK;
+ return msg->size;
+ }
+
+ if (((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE)
+ || ((msg->request & ~DP_AUX_I2C_MOT) ==
+ DP_AUX_I2C_WRITE_STATUS_UPDATE)) {
+
+ u8 i2c_status = 0u;
+ u16 respSize = 0u;
+
+ ret = cdns_mhdp_i2c_write(mhdp, msg->address,
+ msg->buffer,
+ !!(msg->request & DP_AUX_I2C_MOT),
+ msg->size, &respSize);
+
+ if (ret < 0) {
+ dev_err(aux->dev, "cdns_mhdp_i2c_write status %d\n",
+ ret);
+ return -EIO;
+ }
+
+ ret = cdns_mhdp_get_last_i2c_status(mhdp, &i2c_status);
+ if (ret < 0) {
+ dev_err(aux->dev,
+ "cdns_mhdp_get_last_i2c_status status %d\n",
+ ret);
+ return -EIO;
+ }
+
+ switch (i2c_status) {
+ case 0u:
+ msg->reply = DP_AUX_I2C_REPLY_ACK;
+ break;
+ case 1u:
+ msg->reply = DP_AUX_I2C_REPLY_NACK;
+ break;
+ case 2u:
+ msg->reply = DP_AUX_I2C_REPLY_DEFER;
+ break;
+ default:
+ msg->reply = DP_AUX_I2C_REPLY_NACK;
+ break;
+ }
+
+ return respSize;
+ }
+
+ if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_READ) {
+
+ u8 i2c_status = 0u;
+ u16 respSize = 0u;
+
+ ret = cdns_mhdp_i2c_read(mhdp, msg->address, msg->buffer,
+ msg->size,
+ !!(msg->request & DP_AUX_I2C_MOT),
+ &respSize);
+ if (ret < 0)
+ return -EIO;
+
+ ret = cdns_mhdp_get_last_i2c_status(mhdp, &i2c_status);
+
+ if (ret < 0) {
+ dev_err(aux->dev,
+ "cdns_mhdp_get_last_i2c_status ret %d\n", ret);
+ return -EIO;
+ }
+
+ switch (i2c_status) {
+ case 0u:
+ msg->reply = DP_AUX_I2C_REPLY_ACK;
+ break;
+ case 1u:
+ msg->reply = DP_AUX_I2C_REPLY_NACK;
+ break;
+ case 2u:
+ msg->reply = DP_AUX_I2C_REPLY_DEFER;
+ break;
+ default:
+ msg->reply = DP_AUX_I2C_REPLY_NACK;
+ break;
+ }
+
+ return respSize;
+ }
+
+ return 0;
+}
+
+static int dp_aux_init(struct cdns_mhdp_device *mhdp,
+ struct device *dev)
+{
+ int ret;
+
+ mhdp->dp.aux.name = "imx_dp_aux";
+ mhdp->dp.aux.dev = dev;
+ mhdp->dp.aux.drm_dev = mhdp->drm_dev;
+ mhdp->dp.aux.transfer = dp_aux_transfer;
+
+ ret = drm_dp_aux_register(&mhdp->dp.aux);
+
+ return ret;
+}
+
+static int dp_aux_destroy(struct cdns_mhdp_device *mhdp)
+{
+ drm_dp_aux_unregister(&mhdp->dp.aux);
+ return 0;
+}
+
+static void dp_pixel_clk_reset(struct cdns_mhdp_device *mhdp)
+{
+ u32 val;
+
+ /* reset pixel clk */
+ val = cdns_mhdp_reg_read(mhdp, SOURCE_HDTX_CAR);
+ cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, val & 0xFD);
+ cdns_mhdp_reg_write(mhdp, SOURCE_HDTX_CAR, val);
+}
+
+static void cdns_dp_mode_set(struct cdns_mhdp_device *mhdp)
+{
+ u32 lane_mapping = mhdp->lane_mapping;
+ int ret;
+
+ cdns_mhdp_plat_call(mhdp, pclk_rate);
+
+ /* delay for DP FW stable after pixel clock relock */
+ msleep(50);
+
+ dp_pixel_clk_reset(mhdp);
+
+ /* Get DP Caps */
+ ret = drm_dp_dpcd_read(&mhdp->dp.aux, DP_DPCD_REV, mhdp->dp.dpcd,
+ DP_RECEIVER_CAP_SIZE);
+ if (ret < 0) {
+ DRM_ERROR("Failed to get caps %d\n", ret);
+ return;
+ }
+
+ mhdp->dp.rate = drm_dp_max_link_rate(mhdp->dp.dpcd);
+ mhdp->dp.num_lanes = drm_dp_max_lane_count(mhdp->dp.dpcd);
+ mhdp->dp.link_training_type = DP_TX_FULL_LINK_TRAINING;
+
+ /* check the max link rate */
+ if (mhdp->dp.rate > CDNS_DP_MAX_LINK_RATE)
+ mhdp->dp.rate = CDNS_DP_MAX_LINK_RATE;
+
+ /* Initialize link rate/num_lanes as panel max link rate/max_num_lanes */
+ cdns_mhdp_plat_call(mhdp, phy_set);
+
+ /* Video off */
+ ret = cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_IDLE);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed to valid video %d\n", ret);
+ return;
+ }
+
+ /* Line swaping */
+ cdns_mhdp_reg_write(mhdp, LANES_CONFIG, 0x00400000 | lane_mapping);
+
+ /* Set DP host capability */
+ ret = cdns_mhdp_set_host_cap(mhdp);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed to set host cap %d\n", ret);
+ return;
+ }
+
+ ret = cdns_mhdp_config_video(mhdp);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed to config video %d\n", ret);
+ return;
+ }
+
+ return;
+}
+
+void cdns_dp_handle_hpd_irq(struct cdns_mhdp_device *mhdp)
+{
+ u8 status[6];
+
+ mutex_lock(&mhdp->lock);
+ cdns_mhdp_dpcd_read(mhdp, 0x200, &status[0], 6);
+ DRM_DEBUG_DRIVER("DPCD HPD IRQ STATUS: %08x\n", status[1]);
+ cdns_mhdp_dpcd_write(mhdp, 0x201, status[1]);
+ mutex_unlock(&mhdp->lock);
+}
+
+/* -----------------------------------------------------------------------------
+ * dp TX Setup
+ */
+static enum drm_connector_status
+cdns_dp_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct cdns_mhdp_device *mhdp = container_of(connector,
+ struct cdns_mhdp_device, connector.base);
+ u8 hpd = 0xf;
+ mutex_lock(&mhdp->lock);
+ hpd = cdns_mhdp_read_hpd(mhdp);
+ mutex_unlock(&mhdp->lock);
+ DRM_DEBUG_DRIVER("%s hpd = %d\n", __func__, hpd);
+
+ if (mhdp->force_disconnected_sts && (hpd == 1)) {
+ DRM_DEBUG_DRIVER("Returning disconnect status until ready\n");
+ return connector_status_disconnected;
+ }
+ if (hpd == 0)
+ /* Cable Disconnedted */
+ return connector_status_disconnected;
+ else if (hpd == 3) {
+ /* Cable Connected and seen IRQ*/
+ DRM_DEBUG_DRIVER("Warning! Missed HPD IRQ event seen\n");
+ return connector_status_connected;
+ } else if (hpd == 1)
+ /* Cable Connected */
+ return connector_status_connected;
+
+ /* Cable status unknown */
+ DRM_DEBUG_DRIVER("Unknown cable status, hdp=%u\n", hpd);
+ return connector_status_unknown;
+}
+
+static int cdns_dp_connector_get_modes(struct drm_connector *connector)
+{
+ struct cdns_mhdp_device *mhdp = container_of(connector,
+ struct cdns_mhdp_device, connector.base);
+ int num_modes = 0;
+ struct edid *edid;
+
+ edid = drm_do_get_edid(&mhdp->connector.base,
+ cdns_mhdp_get_edid_block, mhdp);
+ if (edid) {
+ DRM_DEBUG_DRIVER("%x,%x,%x,%x,%x,%x,%x,%x\n",
+ edid->header[0], edid->header[1],
+ edid->header[2], edid->header[3],
+ edid->header[4], edid->header[5],
+ edid->header[6], edid->header[7]);
+ drm_connector_update_edid_property(connector, edid);
+ num_modes = drm_add_edid_modes(connector, edid);
+ kfree(edid);
+ }
+
+ if (num_modes == 0)
+ DRM_ERROR("Invalid edid\n");
+ return num_modes;
+}
+
+static bool blob_equal(const struct drm_property_blob *a,
+ const struct drm_property_blob *b)
+{
+ if (a && b)
+ return a->length == b->length &&
+ !memcmp(a->data, b->data, a->length);
+
+ return !a == !b;
+}
+
+static int cdns_dp_connector_atomic_check(struct drm_connector *connector,
+ struct drm_atomic_state *state)
+{
+ struct drm_connector_state *new_con_state =
+ drm_atomic_get_new_connector_state(state, connector);
+ struct drm_connector_state *old_con_state =
+ drm_atomic_get_old_connector_state(state, connector);
+ struct drm_crtc *crtc = new_con_state->crtc;
+ struct drm_crtc_state *new_crtc_state;
+
+ cdns_hdcp_atomic_check(connector, old_con_state, new_con_state);
+ if (!new_con_state->crtc)
+ return 0;
+
+ new_crtc_state = drm_atomic_get_crtc_state(state, crtc);
+ if (IS_ERR(new_crtc_state))
+ return PTR_ERR(new_crtc_state);
+
+ if (!blob_equal(new_con_state->hdr_output_metadata,
+ old_con_state->hdr_output_metadata) ||
+ new_con_state->colorspace != old_con_state->colorspace) {
+
+ new_crtc_state->mode_changed =
+ !new_con_state->hdr_output_metadata ||
+ !old_con_state->hdr_output_metadata ||
+ new_con_state->colorspace != old_con_state->colorspace;
+ }
+
+ /*
+ * These properties are handled by fastset, and might not end up in a
+ * modeset.
+ */
+ if (new_con_state->picture_aspect_ratio !=
+ old_con_state->picture_aspect_ratio ||
+ new_con_state->content_type != old_con_state->content_type ||
+ new_con_state->scaling_mode != old_con_state->scaling_mode)
+ new_crtc_state->mode_changed = true;
+ return 0;
+}
+
+static const struct drm_connector_funcs cdns_dp_connector_funcs = {
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .detect = cdns_dp_connector_detect,
+ .destroy = drm_connector_cleanup,
+ .reset = drm_atomic_helper_connector_reset,
+ .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
+ .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs cdns_dp_connector_helper_funcs = {
+ .get_modes = cdns_dp_connector_get_modes,
+ .atomic_check = cdns_dp_connector_atomic_check,
+};
+
+static int cdns_dp_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+ struct drm_encoder *encoder = bridge->encoder;
+ struct drm_connector *connector = &mhdp->connector.base;
+ int ret;
+
+ connector->interlace_allowed = 1;
+
+ if (mhdp->is_hpd)
+ connector->polled = DRM_CONNECTOR_POLL_HPD;
+ else
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+
+ drm_connector_helper_add(connector, &cdns_dp_connector_helper_funcs);
+
+ ret = drm_connector_init(bridge->dev, connector, &cdns_dp_connector_funcs,
+ DRM_MODE_CONNECTOR_DisplayPort);
+ if (ret < 0) {
+ DRM_ERROR("Failed to initialize connector\n");
+ return ret;
+ }
+
+ drm_connector_attach_encoder(connector, encoder);
+
+ drm_connector_attach_content_protection_property(connector, true);
+
+ return 0;
+}
+
+static enum drm_mode_status
+cdns_dp_bridge_mode_valid(struct drm_bridge *bridge,
+ const struct drm_display_info *info,
+ const struct drm_display_mode *mode)
+{
+ enum drm_mode_status mode_status = MODE_OK;
+
+ /* We don't support double-clocked modes */
+ if (mode->flags & DRM_MODE_FLAG_DBLCLK ||
+ mode->flags & DRM_MODE_FLAG_INTERLACE)
+ return MODE_BAD;
+
+ /* MAX support pixel clock rate 594MHz */
+ if (mode->clock > 594000)
+ return MODE_CLOCK_HIGH;
+
+ /* 5120 x 2160 is the maximum supported resulution */
+ if (mode->hdisplay > 5120)
+ return MODE_BAD_HVALUE;
+
+ if (mode->vdisplay > 2160)
+ return MODE_BAD_VVALUE;
+
+ return mode_status;
+}
+
+static void cdns_dp_bridge_mode_set(struct drm_bridge *bridge,
+ const struct drm_display_mode *orig_mode,
+ const struct drm_display_mode *mode)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+ struct drm_display_info *display_info = &mhdp->connector.base.display_info;
+ struct video_info *video = &mhdp->video_info;
+
+ switch (display_info->bpc) {
+ case 10:
+ video->color_depth = 10;
+ break;
+ case 6:
+ video->color_depth = 6;
+ break;
+ default:
+ video->color_depth = 8;
+ break;
+ }
+
+ video->color_fmt = PXL_RGB;
+ video->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NVSYNC);
+ video->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_NHSYNC);
+
+ DRM_DEBUG_DRIVER("Mode: %dx%dp%d\n", mode->hdisplay, mode->vdisplay,
+ mode->clock);
+ memcpy(&mhdp->mode, mode, sizeof(struct drm_display_mode));
+
+ mutex_lock(&mhdp->lock);
+ cdns_dp_mode_set(mhdp);
+ mutex_unlock(&mhdp->lock);
+
+ /* reset force mode set flag */
+ mhdp->force_mode_set = false;
+}
+
+static void cdn_dp_bridge_enable(struct drm_bridge *bridge)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+ struct drm_connector_state *conn_state = mhdp->connector.base.state;
+ int ret;
+
+ mutex_lock(&mhdp->lock);
+ /* Link trainning */
+ ret = cdns_mhdp_train_link(mhdp);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed link train %d\n", ret);
+ mutex_unlock(&mhdp->lock);
+ return;
+ }
+ mutex_unlock(&mhdp->lock);
+
+ ret = cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_VALID);
+ if (ret) {
+ DRM_DEV_ERROR(mhdp->dev, "Failed to valid video %d\n", ret);
+ return;
+ }
+
+ if (conn_state->content_protection == DRM_MODE_CONTENT_PROTECTION_DESIRED)
+ cdns_hdcp_enable(mhdp);
+
+}
+
+static void cdn_dp_bridge_disable(struct drm_bridge *bridge)
+{
+ struct cdns_mhdp_device *mhdp = bridge->driver_private;
+
+ cdns_hdcp_disable(mhdp);
+
+ cdns_mhdp_set_video_status(mhdp, CONTROL_VIDEO_IDLE);
+}
+
+static const struct drm_bridge_funcs cdns_dp_bridge_funcs = {
+ .attach = cdns_dp_bridge_attach,
+ .enable = cdn_dp_bridge_enable,
+ .disable = cdn_dp_bridge_disable,
+ .mode_set = cdns_dp_bridge_mode_set,
+ .mode_valid = cdns_dp_bridge_mode_valid,
+};
+
+static void hotplug_work_func(struct work_struct *work)
+{
+ struct cdns_mhdp_device *mhdp = container_of(work,
+ struct cdns_mhdp_device,
+ hotplug_work.work);
+ struct drm_connector *connector = &mhdp->connector.base;
+ enum drm_connector_status connector_sts;
+ int loop_cnt, retry;
+
+ DRM_DEBUG_DRIVER("Starting %s\n", __func__);
+
+ if (connector->status == connector_status_connected) {
+ /* Need to check if we had an IRQ event */
+ u8 hpd = 0xf;
+
+ mutex_lock(&mhdp->lock);
+ hpd = cdns_mhdp_read_hpd(mhdp);
+ mutex_unlock(&mhdp->lock);
+ DRM_DEBUG_DRIVER("cdns_mhdp_read_hpd = %d\n", hpd);
+ if (hpd == 3) {
+ /* Cable Connected and seen IRQ*/
+ DRM_DEBUG_DRIVER("HPD IRQ event seen\n");
+ cdns_dp_handle_hpd_irq(mhdp);
+ connector_sts = connector_status_connected;
+ } else if (hpd == 1)
+ connector_sts = connector_status_connected;
+ else if (hpd == 0)
+ connector_sts = connector_status_disconnected;
+ else
+ connector_sts = connector_status_unknown;
+
+ /* if was connected then there may have been unplug event,
+ * either wait 900us then call cdns_dp_connector_detect and if
+ * still connected then just ignore and finish or poll a
+ * certain number of times. Otherwise set status to unconnected
+ * and call the hotplug_event function.
+ */
+ for (loop_cnt = 0; loop_cnt < CDNS_DP_HPD_POLL_DWN_LOOP;
+ loop_cnt++) {
+ udelay(CDNS_DP_HPD_POLL_DWN_DLY_US);
+ if (loop_cnt > 0)
+ connector_sts =
+ cdns_dp_connector_detect(connector, false);
+ if (connector_sts != connector_status_connected) {
+ DRM_DEBUG_DRIVER("HDMI/DP Cable Plug Out\n");
+ break;
+ }
+ }
+ if (connector_sts == connector_status_connected) {
+ DRM_DEBUG_DRIVER("Finished %s - early\n", __func__);
+ return;
+ }
+ /* Disable HDCP when cable plugout,
+ * set content_protection to DESIRED, recovery HDCP state after cable plugin
+ */
+ if (connector->state->content_protection
+ != DRM_MODE_CONTENT_PROTECTION_UNDESIRED) {
+ connector->state->content_protection = DRM_MODE_CONTENT_PROTECTION_DESIRED;
+ mhdp->hdcp.state = HDCP_STATE_DISABLING;
+ }
+ DRM_DEBUG_DRIVER("Calling drm_kms_helper_hotplug_event\n");
+ /* Note that before we call the helper functions we need
+ * to force the cdns_dp_connector_detect function from
+ * returning a connected status since the DRM functions
+ * still end up calling that in a roundabout way when we
+ * signal a status change. We need to do this to ensure
+ * that a shutdown proper completes and don't end up in
+ * a strange state.
+ */
+ mhdp->force_disconnected_sts = true;
+ connector->status = connector_sts;
+ drm_kms_helper_hotplug_event(connector->dev);
+ /* There was a disconnection so give some time to allow
+ * things to clean up
+ */
+ DRM_DEBUG_DRIVER("Start sleep\n");
+ msleep(75);
+ DRM_DEBUG_DRIVER("End sleep\n");
+ mhdp->force_disconnected_sts = false;
+ } else {
+ /* Disconnected or unknown status */
+ /* if was disconnected possibly a connect event, call
+ * cdns_dp_connector_detect multiple times to validate before
+ * updating to connected status and calling the hotplug event
+ * function. This is needed since the HW/FW can take some time
+ * to validate.
+ */
+ for (loop_cnt = 0; loop_cnt < CDNS_DP_HPD_POLL_UP_LOOP;
+ loop_cnt++) {
+ msleep(CDNS_DP_HPD_POLL_UP_DLY_US / 1000);
+ connector_sts =
+ cdns_dp_connector_detect(connector, false);
+ if (connector_sts == connector_status_connected) {
+ DRM_DEBUG_DRIVER("HDMI/DP Cable Plug In\n");
+ /* Recovery HDCP state */
+ if (connector->state->content_protection
+ != DRM_MODE_CONTENT_PROTECTION_UNDESIRED)
+ mhdp->hdcp.state = HDCP_STATE_ENABLING;
+ break;
+ }
+ }
+ if (connector_sts == connector_status_connected) {
+ DRM_DEBUG_DRIVER("Calling drm_kms_helper_hotplug_event\n");
+ connector->status = connector_sts;
+ drm_kms_helper_hotplug_event(connector->dev);
+ }
+ }
+
+ /* check connection status one more time in case there had been a short
+ * disconnect that might have caused re-connect to be missed, if so
+ * schedule again.
+ */
+ retry = 1;
+ do {
+ connector_sts = cdns_dp_connector_detect(connector, false);
+ if (connector_sts != connector->status) {
+ DRM_DEBUG_DRIVER("Re-queuing work_func due to possible change\n");
+ queue_delayed_work(system_wq, &mhdp->hotplug_work,
+ usecs_to_jiffies(50));
+ break;
+ }
+ retry--;
+ } while (retry > 0);
+ DRM_DEBUG_DRIVER("Finished %s\n", __func__);
+}
+
+static irqreturn_t cdns_dp_irq_thread(int irq, void *data)
+{
+ struct cdns_mhdp_device *mhdp = data;
+
+ disable_irq_nosync(irq);
+
+ /* Need to handle the enable HERE */
+ if (irq == mhdp->irq[IRQ_IN]) {
+ DRM_DEBUG_DRIVER("HDMI/DP IRQ IN\n");
+ enable_irq(mhdp->irq[IRQ_OUT]);
+ } else if (irq == mhdp->irq[IRQ_OUT]) {
+ DRM_DEBUG_DRIVER("HDMI/DP IRQ OUT\n");
+ enable_irq(mhdp->irq[IRQ_IN]);
+ }
+
+ /* Queue job as long as not already in queue */
+ queue_delayed_work(system_wq, &mhdp->hotplug_work,
+ usecs_to_jiffies(5));
+
+ return IRQ_HANDLED;
+}
+
+static void cdns_dp_parse_dt(struct cdns_mhdp_device *mhdp)
+{
+ struct device_node *of_node = mhdp->dev->of_node;
+ int ret;
+
+ ret = of_property_read_u32(of_node, "lane-mapping",
+ &mhdp->lane_mapping);
+ if (ret) {
+ mhdp->lane_mapping = 0xc6;
+ dev_warn(mhdp->dev, "Failed to get lane_mapping - using default 0xc6\n");
+ }
+ dev_info(mhdp->dev, "lane-mapping 0x%02x\n", mhdp->lane_mapping);
+}
+
+static int __cdns_dp_probe(struct platform_device *pdev,
+ struct cdns_mhdp_device *mhdp)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *iores = NULL;
+ int ret;
+
+ mutex_init(&mhdp->lock);
+ mutex_init(&mhdp->api_lock);
+ mutex_init(&mhdp->iolock);
+
+ INIT_DELAYED_WORK(&mhdp->hotplug_work, hotplug_work_func);
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (iores) {
+ mhdp->regs_base = devm_ioremap(dev, iores->start,
+ resource_size(iores));
+ if (IS_ERR(mhdp->regs_base))
+ return -ENOMEM;
+ }
+
+ iores = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (iores) {
+ mhdp->regs_sec = devm_ioremap(dev, iores->start,
+ resource_size(iores));
+ if (IS_ERR(mhdp->regs_sec))
+ return -ENOMEM;
+ }
+
+ mhdp->is_hpd = true;
+ mhdp->is_dp = true;
+ mhdp->is_ls1028a = false;
+
+ mhdp->irq[IRQ_IN] = platform_get_irq_byname(pdev, "plug_in");
+ if (mhdp->irq[IRQ_IN] < 0) {
+ mhdp->is_hpd = false;
+ dev_info(dev, "No plug_in irq number\n");
+ }
+
+ mhdp->irq[IRQ_OUT] = platform_get_irq_byname(pdev, "plug_out");
+ if (mhdp->irq[IRQ_OUT] < 0) {
+ mhdp->is_hpd = false;
+ dev_info(dev, "No plug_out irq number\n");
+ }
+
+ cdns_dp_parse_dt(mhdp);
+
+ cnds_hdcp_create_device_files(mhdp);
+
+ if (of_device_is_compatible(dev->of_node, "cdn,ls1028a-dp"))
+ mhdp->is_ls1028a = true;
+
+ cdns_mhdp_plat_call(mhdp, power_on);
+
+ cdns_mhdp_plat_call(mhdp, firmware_init);
+
+ /* DP FW alive check */
+ ret = cdns_mhdp_check_alive(mhdp);
+ if (ret == false) {
+ DRM_ERROR("NO dp FW running\n");
+ return -ENXIO;
+ }
+
+ ret = cdns_hdcp_init(mhdp, pdev->dev.of_node);
+ if (ret < 0)
+ DRM_WARN("Failed to initialize HDCP\n");
+
+ /* DP PHY init before AUX init */
+ cdns_mhdp_plat_call(mhdp, phy_set);
+
+ /* Enable Hotplug Detect IRQ thread */
+ if (mhdp->is_hpd) {
+ irq_set_status_flags(mhdp->irq[IRQ_IN], IRQ_NOAUTOEN);
+ ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_IN],
+ NULL, cdns_dp_irq_thread,
+ IRQF_ONESHOT, dev_name(dev),
+ mhdp);
+
+ if (ret) {
+ dev_err(dev, "can't claim irq %d\n",
+ mhdp->irq[IRQ_IN]);
+ return -EINVAL;
+ }
+
+ irq_set_status_flags(mhdp->irq[IRQ_OUT], IRQ_NOAUTOEN);
+ ret = devm_request_threaded_irq(dev, mhdp->irq[IRQ_OUT],
+ NULL, cdns_dp_irq_thread,
+ IRQF_ONESHOT, dev_name(dev),
+ mhdp);
+
+ if (ret) {
+ dev_err(dev, "can't claim irq %d\n",
+ mhdp->irq[IRQ_OUT]);
+ return -EINVAL;
+ }
+
+ // Call to clear any latched values first...
+ cdns_mhdp_read_hpd(mhdp);
+ if (cdns_mhdp_read_hpd(mhdp))
+ enable_irq(mhdp->irq[IRQ_OUT]);
+ else
+ enable_irq(mhdp->irq[IRQ_IN]);
+ }
+
+ mhdp->bridge.base.driver_private = mhdp;
+ mhdp->bridge.base.funcs = &cdns_dp_bridge_funcs;
+#ifdef CONFIG_OF
+ mhdp->bridge.base.of_node = dev->of_node;
+#endif
+
+ dev_set_drvdata(dev, mhdp);
+
+ /* register audio driver */
+ cdns_mhdp_register_audio_driver(dev);
+
+ dp_aux_init(mhdp, dev);
+
+ return 0;
+}
+
+static void __cdns_dp_remove(struct cdns_mhdp_device *mhdp)
+{
+ dp_aux_destroy(mhdp);
+ cdns_mhdp_unregister_audio_driver(mhdp->dev);
+}
+
+/* -----------------------------------------------------------------------------
+ * Probe/remove API, used from platforms based on the DRM bridge API.
+ */
+int cdns_dp_probe(struct platform_device *pdev,
+ struct cdns_mhdp_device *mhdp)
+{
+ int ret;
+
+ ret = __cdns_dp_probe(pdev, mhdp);
+ if (ret)
+ return ret;
+
+ drm_bridge_add(&mhdp->bridge.base);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cdns_dp_probe);
+
+void cdns_dp_remove(struct platform_device *pdev)
+{
+ struct cdns_mhdp_device *mhdp = platform_get_drvdata(pdev);
+
+ drm_bridge_remove(&mhdp->bridge.base);
+
+ __cdns_dp_remove(mhdp);
+}
+EXPORT_SYMBOL_GPL(cdns_dp_remove);
+
+/* -----------------------------------------------------------------------------
+ * Bind/unbind API, used from platforms based on the component framework.
+ */
+int cdns_dp_bind(struct platform_device *pdev, struct drm_encoder *encoder,
+ struct cdns_mhdp_device *mhdp)
+{
+ int ret;
+
+ ret = __cdns_dp_probe(pdev, mhdp);
+ if (ret < 0)
+ return ret;
+
+ ret = drm_bridge_attach(encoder, &mhdp->bridge.base, NULL, 0);
+ if (ret) {
+ cdns_dp_remove(pdev);
+ DRM_ERROR("Failed to initialize bridge with drm\n");
+ return ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(cdns_dp_bind);
+
+void cdns_dp_unbind(struct device *dev)
+{
+ struct cdns_mhdp_device *mhdp = dev_get_drvdata(dev);
+
+ __cdns_dp_remove(mhdp);
+}
+EXPORT_SYMBOL_GPL(cdns_dp_unbind);
+
+MODULE_AUTHOR("Sandor Yu <sandor.yu@nxp.com>");
+MODULE_DESCRIPTION("Cadence Display Port transmitter driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:cdn-dp");