| /* |
| * Copyright (c) 2017-2018, 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 <linux/module.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| #include <linux/debugfs.h> |
| #include <linux/component.h> |
| #include <linux/of_irq.h> |
| #include <linux/hdcp_qseecom.h> |
| |
| #include "sde_connector.h" |
| |
| #include "msm_drv.h" |
| #include "dp_usbpd.h" |
| #include "dp_parser.h" |
| #include "dp_power.h" |
| #include "dp_catalog.h" |
| #include "dp_aux.h" |
| #include "dp_link.h" |
| #include "dp_panel.h" |
| #include "dp_ctrl.h" |
| #include "dp_audio.h" |
| #include "dp_display.h" |
| #include "sde_hdcp.h" |
| #include "dp_debug.h" |
| |
| static struct dp_display *g_dp_display; |
| #define HPD_STRING_SIZE 30 |
| |
| struct dp_hdcp { |
| void *data; |
| struct sde_hdcp_ops *ops; |
| |
| void *hdcp1; |
| void *hdcp2; |
| |
| bool feature_enabled; |
| }; |
| |
| struct dp_display_private { |
| char *name; |
| int irq; |
| |
| /* state variables */ |
| bool core_initialized; |
| bool power_on; |
| bool audio_supported; |
| |
| atomic_t aborted; |
| |
| struct platform_device *pdev; |
| struct dentry *root; |
| struct completion notification_comp; |
| |
| struct dp_usbpd *usbpd; |
| struct dp_parser *parser; |
| struct dp_power *power; |
| struct dp_catalog *catalog; |
| struct dp_aux *aux; |
| struct dp_link *link; |
| struct dp_panel *panel; |
| struct dp_ctrl *ctrl; |
| struct dp_audio *audio; |
| struct dp_debug *debug; |
| |
| struct dp_hdcp hdcp; |
| |
| struct dp_usbpd_cb usbpd_cb; |
| struct dp_display_mode mode; |
| struct dp_display dp_display; |
| struct msm_drm_private *priv; |
| |
| struct workqueue_struct *wq; |
| struct delayed_work hdcp_cb_work; |
| struct delayed_work connect_work; |
| struct work_struct attention_work; |
| struct mutex hdcp_mutex; |
| struct mutex session_lock; |
| unsigned long audio_status; |
| }; |
| |
| static const struct of_device_id dp_dt_match[] = { |
| {.compatible = "qcom,dp-display"}, |
| {} |
| }; |
| |
| static bool dp_display_framework_ready(struct dp_display_private *dp) |
| { |
| return dp->dp_display.post_open ? false : true; |
| } |
| |
| static inline bool dp_display_is_hdcp_enabled(struct dp_display_private *dp) |
| { |
| return dp->hdcp.feature_enabled && dp->link->hdcp_status.hdcp_version |
| && dp->hdcp.ops; |
| } |
| |
| static irqreturn_t dp_display_irq(int irq, void *dev_id) |
| { |
| struct dp_display_private *dp = dev_id; |
| |
| if (!dp) { |
| pr_err("invalid data\n"); |
| return IRQ_NONE; |
| } |
| |
| /* DP controller isr */ |
| dp->ctrl->isr(dp->ctrl); |
| |
| /* DP aux isr */ |
| dp->aux->isr(dp->aux); |
| |
| /* HDCP isr */ |
| if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->isr) { |
| if (dp->hdcp.ops->isr(dp->hdcp.data)) |
| pr_err("dp_hdcp_isr failed\n"); |
| } |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void dp_display_hdcp_cb_work(struct work_struct *work) |
| { |
| struct dp_display_private *dp; |
| struct delayed_work *dw = to_delayed_work(work); |
| struct sde_hdcp_ops *ops; |
| int rc = 0; |
| u32 hdcp_auth_state; |
| |
| dp = container_of(dw, struct dp_display_private, hdcp_cb_work); |
| |
| rc = dp->catalog->ctrl.read_hdcp_status(&dp->catalog->ctrl); |
| if (rc >= 0) { |
| hdcp_auth_state = (rc >> 20) & 0x3; |
| pr_debug("hdcp auth state %d\n", hdcp_auth_state); |
| } |
| |
| ops = dp->hdcp.ops; |
| |
| pr_debug("%s: %s\n", |
| sde_hdcp_version(dp->link->hdcp_status.hdcp_version), |
| sde_hdcp_state_name(dp->link->hdcp_status.hdcp_state)); |
| |
| switch (dp->link->hdcp_status.hdcp_state) { |
| case HDCP_STATE_AUTHENTICATING: |
| if (dp->hdcp.ops && dp->hdcp.ops->authenticate) |
| rc = dp->hdcp.ops->authenticate(dp->hdcp.data); |
| break; |
| case HDCP_STATE_AUTH_FAIL: |
| if (dp->power_on) { |
| if (ops && ops->reauthenticate) { |
| rc = ops->reauthenticate(dp->hdcp.data); |
| if (rc) |
| pr_err("failed rc=%d\n", rc); |
| } |
| } else { |
| pr_debug("not reauthenticating, cable disconnected\n"); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void dp_display_notify_hdcp_status_cb(void *ptr, |
| enum sde_hdcp_state state) |
| { |
| struct dp_display_private *dp = ptr; |
| |
| if (!dp) { |
| pr_err("invalid input\n"); |
| return; |
| } |
| |
| dp->link->hdcp_status.hdcp_state = state; |
| |
| if (dp->dp_display.is_connected) |
| queue_delayed_work(dp->wq, &dp->hdcp_cb_work, HZ/4); |
| } |
| |
| static void dp_display_destroy_hdcp_workqueue(struct dp_display_private *dp) |
| { |
| if (dp->wq) |
| destroy_workqueue(dp->wq); |
| } |
| |
| static void dp_display_update_hdcp_info(struct dp_display_private *dp) |
| { |
| void *fd = NULL; |
| struct sde_hdcp_ops *ops = NULL; |
| bool hdcp2_present = false, hdcp1_present = false; |
| |
| if (!dp) { |
| pr_err("invalid input\n"); |
| return; |
| } |
| |
| dp->link->hdcp_status.hdcp_state = HDCP_STATE_INACTIVE; |
| dp->link->hdcp_status.hdcp_version = HDCP_VERSION_NONE; |
| |
| if (!dp->hdcp.feature_enabled) { |
| pr_debug("feature not enabled\n"); |
| return; |
| } |
| |
| fd = dp->hdcp.hdcp2; |
| if (fd) |
| ops = sde_dp_hdcp2p2_start(fd); |
| |
| if (ops && ops->feature_supported) |
| hdcp2_present = ops->feature_supported(fd); |
| else |
| hdcp2_present = false; |
| |
| pr_debug("hdcp2p2: %s\n", |
| hdcp2_present ? "supported" : "not supported"); |
| |
| if (!hdcp2_present) { |
| hdcp1_present = hdcp1_check_if_supported_load_app(); |
| |
| if (hdcp1_present) { |
| fd = dp->hdcp.hdcp1; |
| ops = sde_hdcp_1x_start(fd); |
| dp->link->hdcp_status.hdcp_version = HDCP_VERSION_1X; |
| } |
| } else { |
| dp->link->hdcp_status.hdcp_version = HDCP_VERSION_2P2; |
| } |
| |
| pr_debug("hdcp1x: %s\n", |
| hdcp1_present ? "supported" : "not supported"); |
| |
| if (hdcp2_present || hdcp1_present) { |
| dp->hdcp.data = fd; |
| dp->hdcp.ops = ops; |
| } else { |
| dp->hdcp.data = NULL; |
| dp->hdcp.ops = NULL; |
| } |
| } |
| |
| static void dp_display_deinitialize_hdcp(struct dp_display_private *dp) |
| { |
| if (!dp) { |
| pr_err("invalid input\n"); |
| return; |
| } |
| |
| sde_dp_hdcp2p2_deinit(dp->hdcp.data); |
| dp_display_destroy_hdcp_workqueue(dp); |
| if (&dp->hdcp_mutex) |
| mutex_destroy(&dp->hdcp_mutex); |
| } |
| |
| static int dp_display_initialize_hdcp(struct dp_display_private *dp) |
| { |
| struct sde_hdcp_init_data hdcp_init_data; |
| struct dp_parser *parser; |
| int rc = 0; |
| |
| if (!dp) { |
| pr_err("invalid input\n"); |
| return -EINVAL; |
| } |
| |
| parser = dp->parser; |
| |
| mutex_init(&dp->hdcp_mutex); |
| |
| hdcp_init_data.client_id = HDCP_CLIENT_DP; |
| hdcp_init_data.drm_aux = dp->aux->drm_aux; |
| hdcp_init_data.cb_data = (void *)dp; |
| hdcp_init_data.workq = dp->wq; |
| hdcp_init_data.sec_access = true; |
| hdcp_init_data.notify_status = dp_display_notify_hdcp_status_cb; |
| hdcp_init_data.dp_ahb = &parser->get_io(parser, "dp_ahb")->io; |
| hdcp_init_data.dp_aux = &parser->get_io(parser, "dp_aux")->io; |
| hdcp_init_data.dp_link = &parser->get_io(parser, "dp_link")->io; |
| hdcp_init_data.dp_p0 = &parser->get_io(parser, "dp_p0")->io; |
| hdcp_init_data.qfprom_io = &parser->get_io(parser, |
| "qfprom_physical")->io; |
| hdcp_init_data.hdcp_io = &parser->get_io(parser, |
| "hdcp_physical")->io; |
| hdcp_init_data.revision = &dp->panel->link_info.revision; |
| hdcp_init_data.msm_hdcp_dev = dp->parser->msm_hdcp_dev; |
| |
| dp->hdcp.hdcp1 = sde_hdcp_1x_init(&hdcp_init_data); |
| if (IS_ERR_OR_NULL(dp->hdcp.hdcp1)) { |
| pr_err("Error initializing HDCP 1.x\n"); |
| rc = -EINVAL; |
| goto error; |
| } |
| |
| pr_debug("HDCP 1.3 initialized\n"); |
| |
| dp->hdcp.hdcp2 = sde_dp_hdcp2p2_init(&hdcp_init_data); |
| if (IS_ERR_OR_NULL(dp->hdcp.hdcp2)) { |
| pr_err("Error initializing HDCP 2.x\n"); |
| rc = -EINVAL; |
| goto error; |
| } |
| |
| pr_debug("HDCP 2.2 initialized\n"); |
| |
| dp->hdcp.feature_enabled = true; |
| |
| return 0; |
| error: |
| dp_display_deinitialize_hdcp(dp); |
| return rc; |
| } |
| |
| static int dp_display_bind(struct device *dev, struct device *master, |
| void *data) |
| { |
| int rc = 0; |
| struct dp_display_private *dp; |
| struct drm_device *drm; |
| struct platform_device *pdev = to_platform_device(dev); |
| |
| if (!dev || !pdev || !master) { |
| pr_err("invalid param(s), dev %pK, pdev %pK, master %pK\n", |
| dev, pdev, master); |
| rc = -EINVAL; |
| goto end; |
| } |
| |
| drm = dev_get_drvdata(master); |
| dp = platform_get_drvdata(pdev); |
| if (!drm || !dp) { |
| pr_err("invalid param(s), drm %pK, dp %pK\n", |
| drm, dp); |
| rc = -EINVAL; |
| goto end; |
| } |
| |
| dp->dp_display.drm_dev = drm; |
| dp->priv = drm->dev_private; |
| end: |
| return rc; |
| } |
| |
| static void dp_display_unbind(struct device *dev, struct device *master, |
| void *data) |
| { |
| struct dp_display_private *dp; |
| struct platform_device *pdev = to_platform_device(dev); |
| |
| if (!dev || !pdev) { |
| pr_err("invalid param(s)\n"); |
| return; |
| } |
| |
| dp = platform_get_drvdata(pdev); |
| if (!dp) { |
| pr_err("Invalid params\n"); |
| return; |
| } |
| |
| (void)dp->power->power_client_deinit(dp->power); |
| (void)dp->aux->drm_aux_deregister(dp->aux); |
| dp_display_deinitialize_hdcp(dp); |
| } |
| |
| static const struct component_ops dp_display_comp_ops = { |
| .bind = dp_display_bind, |
| .unbind = dp_display_unbind, |
| }; |
| |
| static bool dp_display_is_ds_bridge(struct dp_panel *panel) |
| { |
| return (panel->dpcd[DP_DOWNSTREAMPORT_PRESENT] & |
| DP_DWN_STRM_PORT_PRESENT); |
| } |
| |
| static bool dp_display_is_sink_count_zero(struct dp_display_private *dp) |
| { |
| return dp_display_is_ds_bridge(dp->panel) && |
| (dp->link->sink_count.count == 0); |
| } |
| |
| static void dp_display_send_hpd_event(struct dp_display_private *dp) |
| { |
| struct drm_device *dev = NULL; |
| struct drm_connector *connector; |
| char name[HPD_STRING_SIZE], status[HPD_STRING_SIZE], |
| bpp[HPD_STRING_SIZE], pattern[HPD_STRING_SIZE]; |
| char *envp[5]; |
| |
| connector = dp->dp_display.connector; |
| |
| if (!connector) { |
| pr_err("connector not set\n"); |
| return; |
| } |
| |
| connector->status = connector->funcs->detect(connector, false); |
| |
| dev = dp->dp_display.connector->dev; |
| |
| snprintf(name, HPD_STRING_SIZE, "name=%s", connector->name); |
| snprintf(status, HPD_STRING_SIZE, "status=%s", |
| drm_get_connector_status_name(connector->status)); |
| snprintf(bpp, HPD_STRING_SIZE, "bpp=%d", |
| dp_link_bit_depth_to_bpp( |
| dp->link->test_video.test_bit_depth)); |
| snprintf(pattern, HPD_STRING_SIZE, "pattern=%d", |
| dp->link->test_video.test_video_pattern); |
| |
| pr_debug("[%s]:[%s] [%s] [%s]\n", name, status, bpp, pattern); |
| envp[0] = name; |
| envp[1] = status; |
| envp[2] = bpp; |
| envp[3] = pattern; |
| envp[4] = NULL; |
| kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, |
| envp); |
| } |
| |
| static void dp_display_post_open(struct dp_display *dp_display) |
| { |
| struct drm_connector *connector; |
| struct dp_display_private *dp; |
| |
| if (!dp_display) { |
| pr_err("invalid input\n"); |
| return; |
| } |
| |
| dp = container_of(dp_display, struct dp_display_private, dp_display); |
| if (IS_ERR_OR_NULL(dp)) { |
| pr_err("invalid params\n"); |
| return; |
| } |
| |
| connector = dp->dp_display.connector; |
| |
| if (!connector) { |
| pr_err("connector not set\n"); |
| return; |
| } |
| |
| /* if cable is already connected, send notification */ |
| if (dp->usbpd->hpd_high) |
| queue_delayed_work(dp->wq, &dp->connect_work, HZ * 10); |
| else |
| dp_display->post_open = NULL; |
| } |
| |
| static int dp_display_send_hpd_notification(struct dp_display_private *dp, |
| bool hpd) |
| { |
| u32 timeout_sec; |
| int ret = 0; |
| |
| dp->dp_display.is_connected = hpd; |
| |
| if (dp_display_framework_ready(dp)) |
| timeout_sec = 5; |
| else |
| timeout_sec = 10; |
| |
| dp->aux->state |= DP_STATE_NOTIFICATION_SENT; |
| |
| reinit_completion(&dp->notification_comp); |
| dp_display_send_hpd_event(dp); |
| |
| if (!wait_for_completion_timeout(&dp->notification_comp, |
| HZ * timeout_sec)) { |
| pr_warn("%s timeout\n", hpd ? "connect" : "disconnect"); |
| ret = -EINVAL; |
| } |
| |
| dp->aux->state &= ~DP_STATE_NOTIFICATION_SENT; |
| |
| return ret; |
| } |
| |
| static int dp_display_process_hpd_high(struct dp_display_private *dp) |
| { |
| int rc = 0; |
| struct edid *edid; |
| |
| dp->aux->init(dp->aux, dp->parser->aux_cfg); |
| |
| if (dp->debug->psm_enabled) { |
| dp->link->psm_config(dp->link, &dp->panel->link_info, false); |
| dp->debug->psm_enabled = false; |
| } |
| |
| if (!dp->dp_display.connector) |
| return 0; |
| |
| rc = dp->panel->read_sink_caps(dp->panel, |
| dp->dp_display.connector, dp->usbpd->multi_func); |
| if (rc) { |
| /* |
| * ETIMEDOUT --> cable may have been removed |
| * ENOTCONN --> no downstream device connected |
| */ |
| if (rc == -ETIMEDOUT || rc == -ENOTCONN) |
| goto end; |
| else |
| goto notify; |
| } |
| |
| edid = dp->panel->edid_ctrl->edid; |
| |
| dp->audio_supported = drm_detect_monitor_audio(edid); |
| |
| dp->link->process_request(dp->link); |
| dp->panel->handle_sink_request(dp->panel); |
| |
| dp->dp_display.max_pclk_khz = dp->parser->max_pclk_khz; |
| notify: |
| dp_display_send_hpd_notification(dp, true); |
| |
| end: |
| return rc; |
| } |
| |
| static void dp_display_host_init(struct dp_display_private *dp) |
| { |
| bool flip = false; |
| bool reset; |
| |
| if (dp->core_initialized) { |
| pr_debug("DP core already initialized\n"); |
| return; |
| } |
| |
| if (dp->usbpd->orientation == ORIENTATION_CC2) |
| flip = true; |
| |
| reset = dp->debug->sim_mode ? false : !dp->usbpd->multi_func; |
| |
| dp->power->init(dp->power, flip); |
| dp->ctrl->init(dp->ctrl, flip, reset); |
| enable_irq(dp->irq); |
| dp->core_initialized = true; |
| } |
| |
| static void dp_display_host_deinit(struct dp_display_private *dp) |
| { |
| if (!dp->core_initialized) { |
| pr_debug("DP core already off\n"); |
| return; |
| } |
| |
| dp->ctrl->deinit(dp->ctrl); |
| dp->power->deinit(dp->power); |
| disable_irq(dp->irq); |
| dp->core_initialized = false; |
| dp->aux->state = 0; |
| } |
| |
| static int dp_display_process_hpd_low(struct dp_display_private *dp) |
| { |
| int rc = 0; |
| |
| if (!dp->dp_display.is_connected) { |
| pr_debug("HPD already off\n"); |
| return 0; |
| } |
| |
| if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->off) |
| dp->hdcp.ops->off(dp->hdcp.data); |
| |
| if (dp->audio_supported) |
| dp->audio->off(dp->audio); |
| |
| dp->audio_status = -ENODEV; |
| |
| rc = dp_display_send_hpd_notification(dp, false); |
| |
| dp->panel->video_test = false; |
| |
| return rc; |
| } |
| |
| static int dp_display_usbpd_configure_cb(struct device *dev) |
| { |
| int rc = 0; |
| struct dp_display_private *dp; |
| |
| if (!dev) { |
| pr_err("invalid dev\n"); |
| rc = -EINVAL; |
| goto end; |
| } |
| |
| dp = dev_get_drvdata(dev); |
| if (!dp) { |
| pr_err("no driver data found\n"); |
| rc = -ENODEV; |
| goto end; |
| } |
| |
| dp_display_host_init(dp); |
| |
| /* check for hpd high and framework ready */ |
| if (dp->usbpd->hpd_high && dp_display_framework_ready(dp)) |
| queue_delayed_work(dp->wq, &dp->connect_work, 0); |
| end: |
| return rc; |
| } |
| |
| static void dp_display_clean(struct dp_display_private *dp) |
| { |
| if (dp_display_is_hdcp_enabled(dp)) { |
| dp->link->hdcp_status.hdcp_state = HDCP_STATE_INACTIVE; |
| |
| cancel_delayed_work_sync(&dp->hdcp_cb_work); |
| if (dp->hdcp.ops->off) |
| dp->hdcp.ops->off(dp->hdcp.data); |
| } |
| |
| dp->ctrl->push_idle(dp->ctrl); |
| dp->ctrl->off(dp->ctrl); |
| dp->panel->deinit(dp->panel); |
| dp->aux->deinit(dp->aux); |
| dp->power_on = false; |
| } |
| |
| static int dp_display_handle_disconnect(struct dp_display_private *dp) |
| { |
| int rc; |
| |
| rc = dp_display_process_hpd_low(dp); |
| if (rc) { |
| /* cancel any pending request */ |
| dp->ctrl->abort(dp->ctrl); |
| dp->aux->abort(dp->aux); |
| } |
| |
| mutex_lock(&dp->session_lock); |
| if (rc && dp->power_on) |
| dp_display_clean(dp); |
| |
| if (!dp->usbpd->alt_mode_cfg_done) |
| dp_display_host_deinit(dp); |
| |
| mutex_unlock(&dp->session_lock); |
| |
| return rc; |
| } |
| |
| static int dp_display_usbpd_disconnect_cb(struct device *dev) |
| { |
| int rc = 0; |
| struct dp_display_private *dp; |
| |
| if (!dev) { |
| pr_err("invalid dev\n"); |
| rc = -EINVAL; |
| goto end; |
| } |
| |
| dp = dev_get_drvdata(dev); |
| if (!dp) { |
| pr_err("no driver data found\n"); |
| rc = -ENODEV; |
| goto end; |
| } |
| |
| /* |
| * In case cable/dongle is disconnected during adb shell stop, |
| * reset psm_enabled flag to false since it is no more needed |
| */ |
| if (dp->dp_display.post_open) |
| dp->debug->psm_enabled = false; |
| |
| if (dp->debug->psm_enabled) |
| dp->link->psm_config(dp->link, &dp->panel->link_info, true); |
| |
| /* cancel any pending request */ |
| atomic_set(&dp->aborted, 1); |
| dp->ctrl->abort(dp->ctrl); |
| dp->aux->abort(dp->aux); |
| |
| /* wait for idle state */ |
| cancel_delayed_work(&dp->connect_work); |
| flush_workqueue(dp->wq); |
| |
| dp_display_handle_disconnect(dp); |
| atomic_set(&dp->aborted, 0); |
| end: |
| return rc; |
| } |
| |
| static void dp_display_handle_maintenance_req(struct dp_display_private *dp) |
| { |
| mutex_lock(&dp->audio->ops_lock); |
| |
| if (dp->audio_supported && !IS_ERR_VALUE(dp->audio_status)) |
| dp->audio->off(dp->audio); |
| |
| dp->ctrl->link_maintenance(dp->ctrl); |
| |
| if (dp->audio_supported && !IS_ERR_VALUE(dp->audio_status)) |
| dp->audio_status = dp->audio->on(dp->audio); |
| |
| mutex_unlock(&dp->audio->ops_lock); |
| } |
| |
| static void dp_display_attention_work(struct work_struct *work) |
| { |
| struct dp_display_private *dp = container_of(work, |
| struct dp_display_private, attention_work); |
| |
| if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->cp_irq) { |
| if (!dp->hdcp.ops->cp_irq(dp->hdcp.data)) |
| return; |
| } |
| |
| if (dp->link->sink_request & DS_PORT_STATUS_CHANGED) { |
| dp_display_handle_disconnect(dp); |
| |
| if (dp_display_is_sink_count_zero(dp)) { |
| pr_debug("sink count is zero, nothing to do\n"); |
| return; |
| } |
| |
| queue_delayed_work(dp->wq, &dp->connect_work, 0); |
| return; |
| } |
| |
| if (dp->link->sink_request & DP_TEST_LINK_VIDEO_PATTERN) { |
| dp_display_handle_disconnect(dp); |
| |
| dp->panel->video_test = true; |
| dp_display_send_hpd_notification(dp, true); |
| dp->link->send_test_response(dp->link); |
| |
| return; |
| } |
| |
| if (dp->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { |
| dp->ctrl->process_phy_test_request(dp->ctrl); |
| return; |
| } |
| |
| if (dp->link->sink_request & DP_LINK_STATUS_UPDATED) { |
| dp_display_handle_maintenance_req(dp); |
| return; |
| } |
| |
| if (dp->link->sink_request & DP_TEST_LINK_TRAINING) { |
| dp->link->send_test_response(dp->link); |
| dp_display_handle_maintenance_req(dp); |
| return; |
| } |
| } |
| |
| static int dp_display_usbpd_attention_cb(struct device *dev) |
| { |
| struct dp_display_private *dp; |
| |
| if (!dev) { |
| pr_err("invalid dev\n"); |
| return -EINVAL; |
| } |
| |
| dp = dev_get_drvdata(dev); |
| if (!dp) { |
| pr_err("no driver data found\n"); |
| return -ENODEV; |
| } |
| |
| /* check if framework is ready */ |
| if (!dp_display_framework_ready(dp)) { |
| pr_err("framework not ready\n"); |
| return -ENODEV; |
| } |
| |
| if (dp->usbpd->hpd_irq && dp->usbpd->hpd_high && |
| dp->power_on) { |
| dp->link->process_request(dp->link); |
| queue_work(dp->wq, &dp->attention_work); |
| } else if (dp->usbpd->hpd_high) { |
| queue_delayed_work(dp->wq, &dp->connect_work, 0); |
| } else { |
| /* cancel any pending request */ |
| atomic_set(&dp->aborted, 1); |
| dp->ctrl->abort(dp->ctrl); |
| dp->aux->abort(dp->aux); |
| |
| /* wait for idle state */ |
| cancel_delayed_work(&dp->connect_work); |
| flush_workqueue(dp->wq); |
| |
| dp_display_handle_disconnect(dp); |
| atomic_set(&dp->aborted, 0); |
| } |
| |
| return 0; |
| } |
| |
| static void dp_display_connect_work(struct work_struct *work) |
| { |
| struct delayed_work *dw = to_delayed_work(work); |
| struct dp_display_private *dp = container_of(dw, |
| struct dp_display_private, connect_work); |
| |
| if (dp->dp_display.is_connected && dp_display_framework_ready(dp)) { |
| pr_debug("HPD already on\n"); |
| return; |
| } |
| |
| if (atomic_read(&dp->aborted)) { |
| pr_err("aborted\n"); |
| return; |
| } |
| |
| dp_display_process_hpd_high(dp); |
| } |
| |
| static void dp_display_deinit_sub_modules(struct dp_display_private *dp) |
| { |
| dp_audio_put(dp->audio); |
| dp_ctrl_put(dp->ctrl); |
| dp_link_put(dp->link); |
| dp_panel_put(dp->panel); |
| dp_aux_put(dp->aux); |
| dp_power_put(dp->power); |
| dp_catalog_put(dp->catalog); |
| dp_parser_put(dp->parser); |
| dp_usbpd_put(dp->usbpd); |
| mutex_destroy(&dp->session_lock); |
| dp_debug_put(dp->debug); |
| } |
| |
| static int dp_init_sub_modules(struct dp_display_private *dp) |
| { |
| int rc = 0; |
| struct device *dev = &dp->pdev->dev; |
| struct dp_usbpd_cb *cb = &dp->usbpd_cb; |
| struct dp_ctrl_in ctrl_in = { |
| .dev = dev, |
| }; |
| struct dp_panel_in panel_in = { |
| .dev = dev, |
| }; |
| |
| mutex_init(&dp->session_lock); |
| |
| dp->parser = dp_parser_get(dp->pdev); |
| if (IS_ERR(dp->parser)) { |
| rc = PTR_ERR(dp->parser); |
| pr_err("failed to initialize parser, rc = %d\n", rc); |
| dp->parser = NULL; |
| goto error; |
| } |
| |
| rc = dp->parser->parse(dp->parser); |
| if (rc) { |
| pr_err("device tree parsing failed\n"); |
| goto error_catalog; |
| } |
| |
| dp->catalog = dp_catalog_get(dev, dp->parser); |
| if (IS_ERR(dp->catalog)) { |
| rc = PTR_ERR(dp->catalog); |
| pr_err("failed to initialize catalog, rc = %d\n", rc); |
| dp->catalog = NULL; |
| goto error_catalog; |
| } |
| |
| dp->power = dp_power_get(dp->parser); |
| if (IS_ERR(dp->power)) { |
| rc = PTR_ERR(dp->power); |
| pr_err("failed to initialize power, rc = %d\n", rc); |
| dp->power = NULL; |
| goto error_power; |
| } |
| |
| rc = dp->power->power_client_init(dp->power, &dp->priv->phandle); |
| if (rc) { |
| pr_err("Power client create failed\n"); |
| goto error_aux; |
| } |
| |
| dp->aux = dp_aux_get(dev, &dp->catalog->aux, dp->parser->aux_cfg); |
| if (IS_ERR(dp->aux)) { |
| rc = PTR_ERR(dp->aux); |
| pr_err("failed to initialize aux, rc = %d\n", rc); |
| dp->aux = NULL; |
| goto error_aux; |
| } |
| |
| rc = dp->aux->drm_aux_register(dp->aux); |
| if (rc) { |
| pr_err("DRM DP AUX register failed\n"); |
| goto error_link; |
| } |
| |
| dp->link = dp_link_get(dev, dp->aux); |
| if (IS_ERR(dp->link)) { |
| rc = PTR_ERR(dp->link); |
| pr_err("failed to initialize link, rc = %d\n", rc); |
| dp->link = NULL; |
| goto error_link; |
| } |
| |
| panel_in.aux = dp->aux; |
| panel_in.catalog = &dp->catalog->panel; |
| panel_in.link = dp->link; |
| |
| dp->panel = dp_panel_get(&panel_in); |
| if (IS_ERR(dp->panel)) { |
| rc = PTR_ERR(dp->panel); |
| pr_err("failed to initialize panel, rc = %d\n", rc); |
| dp->panel = NULL; |
| goto error_panel; |
| } |
| |
| ctrl_in.link = dp->link; |
| ctrl_in.panel = dp->panel; |
| ctrl_in.aux = dp->aux; |
| ctrl_in.power = dp->power; |
| ctrl_in.catalog = &dp->catalog->ctrl; |
| ctrl_in.parser = dp->parser; |
| |
| dp->ctrl = dp_ctrl_get(&ctrl_in); |
| if (IS_ERR(dp->ctrl)) { |
| rc = PTR_ERR(dp->ctrl); |
| pr_err("failed to initialize ctrl, rc = %d\n", rc); |
| dp->ctrl = NULL; |
| goto error_ctrl; |
| } |
| |
| dp->audio = dp_audio_get(dp->pdev, dp->panel, &dp->catalog->audio); |
| if (IS_ERR(dp->audio)) { |
| rc = PTR_ERR(dp->audio); |
| pr_err("failed to initialize audio, rc = %d\n", rc); |
| dp->audio = NULL; |
| goto error_audio; |
| } |
| |
| cb->configure = dp_display_usbpd_configure_cb; |
| cb->disconnect = dp_display_usbpd_disconnect_cb; |
| cb->attention = dp_display_usbpd_attention_cb; |
| |
| dp->usbpd = dp_usbpd_get(dev, cb); |
| if (IS_ERR(dp->usbpd)) { |
| rc = PTR_ERR(dp->usbpd); |
| pr_err("failed to initialize usbpd, rc = %d\n", rc); |
| dp->usbpd = NULL; |
| goto error_usbpd; |
| } |
| |
| dp->debug = dp_debug_get(dev, dp->panel, dp->usbpd, |
| dp->link, dp->aux, &dp->dp_display.connector, |
| dp->catalog); |
| if (IS_ERR(dp->debug)) { |
| rc = PTR_ERR(dp->debug); |
| pr_err("failed to initialize debug, rc = %d\n", rc); |
| dp->debug = NULL; |
| goto error_debug; |
| } |
| |
| return rc; |
| error_debug: |
| dp_usbpd_put(dp->usbpd); |
| error_usbpd: |
| dp_audio_put(dp->audio); |
| error_audio: |
| dp_ctrl_put(dp->ctrl); |
| error_ctrl: |
| dp_panel_put(dp->panel); |
| error_panel: |
| dp_link_put(dp->link); |
| error_link: |
| dp_aux_put(dp->aux); |
| error_aux: |
| dp_power_put(dp->power); |
| error_power: |
| dp_catalog_put(dp->catalog); |
| error_catalog: |
| dp_parser_put(dp->parser); |
| error: |
| mutex_destroy(&dp->session_lock); |
| return rc; |
| } |
| |
| static void dp_display_post_init(struct dp_display *dp_display) |
| { |
| int rc = 0; |
| struct dp_display_private *dp; |
| |
| if (!dp_display) { |
| pr_err("invalid input\n"); |
| rc = -EINVAL; |
| goto end; |
| } |
| |
| dp = container_of(dp_display, struct dp_display_private, dp_display); |
| if (IS_ERR_OR_NULL(dp)) { |
| pr_err("invalid params\n"); |
| rc = -EINVAL; |
| goto end; |
| } |
| |
| rc = dp_init_sub_modules(dp); |
| if (rc) |
| goto end; |
| |
| dp_display_initialize_hdcp(dp); |
| |
| dp_display->post_init = NULL; |
| end: |
| pr_debug("%s\n", rc ? "failed" : "success"); |
| } |
| |
| static int dp_display_set_mode(struct dp_display *dp_display, |
| struct dp_display_mode *mode) |
| { |
| const u32 num_components = 3, default_bpp = 24; |
| struct dp_display_private *dp; |
| |
| if (!dp_display) { |
| pr_err("invalid input\n"); |
| return -EINVAL; |
| } |
| dp = container_of(dp_display, struct dp_display_private, dp_display); |
| |
| mutex_lock(&dp->session_lock); |
| mode->timing.bpp = |
| dp_display->connector->display_info.bpc * num_components; |
| if (!mode->timing.bpp) |
| mode->timing.bpp = default_bpp; |
| |
| mode->timing.bpp = dp->panel->get_mode_bpp(dp->panel, |
| mode->timing.bpp, mode->timing.pixel_clk_khz); |
| |
| dp->panel->pinfo = mode->timing; |
| dp->panel->init(dp->panel); |
| mutex_unlock(&dp->session_lock); |
| |
| return 0; |
| } |
| |
| static int dp_display_prepare(struct dp_display *dp) |
| { |
| return 0; |
| } |
| |
| static int dp_display_enable(struct dp_display *dp_display) |
| { |
| int rc = 0; |
| struct dp_display_private *dp; |
| |
| if (!dp_display) { |
| pr_err("invalid input\n"); |
| return -EINVAL; |
| } |
| |
| dp = container_of(dp_display, struct dp_display_private, dp_display); |
| |
| mutex_lock(&dp->session_lock); |
| |
| if (dp->power_on) { |
| pr_debug("Link already setup, return\n"); |
| goto end; |
| } |
| |
| if (atomic_read(&dp->aborted)) { |
| pr_err("aborted\n"); |
| goto end; |
| } |
| |
| dp->aux->init(dp->aux, dp->parser->aux_cfg); |
| |
| if (dp->debug->psm_enabled) { |
| dp->link->psm_config(dp->link, &dp->panel->link_info, false); |
| dp->debug->psm_enabled = false; |
| } |
| |
| rc = dp->ctrl->on(dp->ctrl); |
| |
| if (dp->debug->tpg_state) |
| dp->panel->tpg_config(dp->panel, true); |
| |
| if (!rc) |
| dp->power_on = true; |
| end: |
| mutex_unlock(&dp->session_lock); |
| return rc; |
| } |
| |
| static int dp_display_post_enable(struct dp_display *dp_display) |
| { |
| struct dp_display_private *dp; |
| |
| if (!dp_display) { |
| pr_err("invalid input\n"); |
| return -EINVAL; |
| } |
| |
| dp = container_of(dp_display, struct dp_display_private, dp_display); |
| |
| mutex_lock(&dp->session_lock); |
| |
| if (!dp->power_on) { |
| pr_debug("Link not setup, return\n"); |
| goto end; |
| } |
| |
| if (atomic_read(&dp->aborted)) { |
| pr_err("aborted\n"); |
| goto end; |
| } |
| |
| dp->panel->spd_config(dp->panel); |
| |
| if (dp->audio_supported) { |
| dp->audio->bw_code = dp->link->link_params.bw_code; |
| dp->audio->lane_count = dp->link->link_params.lane_count; |
| dp->audio_status = dp->audio->on(dp->audio); |
| } |
| |
| dp_display_update_hdcp_info(dp); |
| |
| if (dp_display_is_hdcp_enabled(dp)) { |
| cancel_delayed_work_sync(&dp->hdcp_cb_work); |
| |
| dp->link->hdcp_status.hdcp_state = HDCP_STATE_AUTHENTICATING; |
| queue_delayed_work(dp->wq, &dp->hdcp_cb_work, HZ / 2); |
| } |
| |
| dp->panel->setup_hdr(dp->panel, NULL); |
| end: |
| /* clear framework event notifier */ |
| dp_display->post_open = NULL; |
| dp->aux->state |= DP_STATE_CTRL_POWERED_ON; |
| |
| complete_all(&dp->notification_comp); |
| mutex_unlock(&dp->session_lock); |
| return 0; |
| } |
| |
| static int dp_display_pre_disable(struct dp_display *dp_display) |
| { |
| struct dp_display_private *dp; |
| |
| if (!dp_display) { |
| pr_err("invalid input\n"); |
| return -EINVAL; |
| } |
| |
| dp = container_of(dp_display, struct dp_display_private, dp_display); |
| |
| mutex_lock(&dp->session_lock); |
| |
| if (!dp->power_on) { |
| pr_debug("Link already powered off, return\n"); |
| goto end; |
| } |
| |
| if (dp_display_is_hdcp_enabled(dp)) { |
| dp->link->hdcp_status.hdcp_state = HDCP_STATE_INACTIVE; |
| |
| cancel_delayed_work_sync(&dp->hdcp_cb_work); |
| if (dp->hdcp.ops->off) |
| dp->hdcp.ops->off(dp->hdcp.data); |
| } |
| |
| if (dp->usbpd->hpd_high && dp->usbpd->alt_mode_cfg_done) { |
| if (dp->audio_supported) |
| dp->audio->off(dp->audio); |
| |
| dp->link->psm_config(dp->link, &dp->panel->link_info, true); |
| dp->debug->psm_enabled = true; |
| } |
| |
| dp->ctrl->push_idle(dp->ctrl); |
| end: |
| mutex_unlock(&dp->session_lock); |
| return 0; |
| } |
| |
| static int dp_display_disable(struct dp_display *dp_display) |
| { |
| struct dp_display_private *dp; |
| struct drm_connector *connector; |
| struct sde_connector_state *c_state; |
| |
| if (!dp_display) { |
| pr_err("invalid input\n"); |
| return -EINVAL; |
| } |
| |
| dp = container_of(dp_display, struct dp_display_private, dp_display); |
| connector = dp->dp_display.connector; |
| c_state = to_sde_connector_state(connector->state); |
| |
| mutex_lock(&dp->session_lock); |
| |
| if (!dp->power_on || !dp->core_initialized) { |
| pr_debug("Link already powered off, return\n"); |
| goto end; |
| } |
| |
| dp->ctrl->off(dp->ctrl); |
| dp->panel->deinit(dp->panel); |
| dp->aux->deinit(dp->aux); |
| |
| connector->hdr_eotf = 0; |
| connector->hdr_metadata_type_one = 0; |
| connector->hdr_max_luminance = 0; |
| connector->hdr_avg_luminance = 0; |
| connector->hdr_min_luminance = 0; |
| |
| memset(&c_state->hdr_meta, 0, sizeof(c_state->hdr_meta)); |
| |
| /* |
| * In case of framework reboot, the DP off sequence is executed without |
| * any notification from driver. Initialize post_open callback to notify |
| * DP connection once framework restarts. |
| */ |
| if (dp->usbpd->hpd_high && dp->usbpd->alt_mode_cfg_done) |
| dp_display->post_open = dp_display_post_open; |
| |
| dp->power_on = false; |
| dp->aux->state = DP_STATE_CTRL_POWERED_OFF; |
| end: |
| complete_all(&dp->notification_comp); |
| mutex_unlock(&dp->session_lock); |
| return 0; |
| } |
| |
| static int dp_request_irq(struct dp_display *dp_display) |
| { |
| int rc = 0; |
| struct dp_display_private *dp; |
| |
| if (!dp_display) { |
| pr_err("invalid input\n"); |
| return -EINVAL; |
| } |
| |
| dp = container_of(dp_display, struct dp_display_private, dp_display); |
| |
| dp->irq = irq_of_parse_and_map(dp->pdev->dev.of_node, 0); |
| if (dp->irq < 0) { |
| rc = dp->irq; |
| pr_err("failed to get irq: %d\n", rc); |
| return rc; |
| } |
| |
| rc = devm_request_irq(&dp->pdev->dev, dp->irq, dp_display_irq, |
| IRQF_TRIGGER_HIGH, "dp_display_isr", dp); |
| if (rc < 0) { |
| pr_err("failed to request IRQ%u: %d\n", |
| dp->irq, rc); |
| return rc; |
| } |
| disable_irq(dp->irq); |
| |
| return 0; |
| } |
| |
| static struct dp_debug *dp_get_debug(struct dp_display *dp_display) |
| { |
| struct dp_display_private *dp; |
| |
| if (!dp_display) { |
| pr_err("invalid input\n"); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| dp = container_of(dp_display, struct dp_display_private, dp_display); |
| |
| return dp->debug; |
| } |
| |
| static int dp_display_unprepare(struct dp_display *dp) |
| { |
| return 0; |
| } |
| |
| static int dp_display_validate_mode(struct dp_display *dp, u32 mode_pclk_khz) |
| { |
| const u32 num_components = 3, default_bpp = 24; |
| struct dp_display_private *dp_display; |
| struct drm_dp_link *link_info; |
| u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; |
| |
| if (!dp || !mode_pclk_khz || !dp->connector) { |
| pr_err("invalid params\n"); |
| return -EINVAL; |
| } |
| |
| dp_display = container_of(dp, struct dp_display_private, dp_display); |
| link_info = &dp_display->panel->link_info; |
| |
| mode_bpp = dp->connector->display_info.bpc * num_components; |
| if (!mode_bpp) |
| mode_bpp = default_bpp; |
| |
| mode_bpp = dp_display->panel->get_mode_bpp(dp_display->panel, |
| mode_bpp, mode_pclk_khz); |
| |
| mode_rate_khz = mode_pclk_khz * mode_bpp; |
| supported_rate_khz = link_info->num_lanes * link_info->rate * 8; |
| |
| if (mode_rate_khz > supported_rate_khz) |
| return MODE_BAD; |
| |
| return MODE_OK; |
| } |
| |
| static int dp_display_get_modes(struct dp_display *dp, |
| struct dp_display_mode *dp_mode) |
| { |
| struct dp_display_private *dp_display; |
| int ret = 0; |
| |
| if (!dp || !dp->connector) { |
| pr_err("invalid params\n"); |
| return 0; |
| } |
| |
| dp_display = container_of(dp, struct dp_display_private, dp_display); |
| |
| ret = dp_display->panel->get_modes(dp_display->panel, |
| dp->connector, dp_mode); |
| if (dp_mode->timing.pixel_clk_khz) |
| dp->max_pclk_khz = dp_mode->timing.pixel_clk_khz; |
| return ret; |
| } |
| |
| static int dp_display_config_hdr(struct dp_display *dp_display, |
| struct drm_msm_ext_hdr_metadata *hdr) |
| { |
| int rc = 0; |
| struct dp_display_private *dp; |
| |
| if (!dp_display) { |
| pr_err("invalid input\n"); |
| return -EINVAL; |
| } |
| |
| dp = container_of(dp_display, struct dp_display_private, dp_display); |
| |
| rc = dp->panel->setup_hdr(dp->panel, hdr); |
| |
| return rc; |
| } |
| |
| static int dp_display_create_workqueue(struct dp_display_private *dp) |
| { |
| dp->wq = create_singlethread_workqueue("drm_dp"); |
| if (IS_ERR_OR_NULL(dp->wq)) { |
| pr_err("Error creating wq\n"); |
| return -EPERM; |
| } |
| |
| INIT_DELAYED_WORK(&dp->hdcp_cb_work, dp_display_hdcp_cb_work); |
| INIT_DELAYED_WORK(&dp->connect_work, dp_display_connect_work); |
| INIT_WORK(&dp->attention_work, dp_display_attention_work); |
| |
| return 0; |
| } |
| |
| static int dp_display_probe(struct platform_device *pdev) |
| { |
| int rc = 0; |
| struct dp_display_private *dp; |
| |
| if (!pdev || !pdev->dev.of_node) { |
| pr_err("pdev not found\n"); |
| rc = -ENODEV; |
| goto bail; |
| } |
| |
| dp = devm_kzalloc(&pdev->dev, sizeof(*dp), GFP_KERNEL); |
| if (!dp) { |
| rc = -ENOMEM; |
| goto bail; |
| } |
| |
| init_completion(&dp->notification_comp); |
| |
| dp->pdev = pdev; |
| dp->name = "drm_dp"; |
| dp->audio_status = -ENODEV; |
| atomic_set(&dp->aborted, 0); |
| |
| rc = dp_display_create_workqueue(dp); |
| if (rc) { |
| pr_err("Failed to create workqueue\n"); |
| goto error; |
| } |
| |
| platform_set_drvdata(pdev, dp); |
| |
| g_dp_display = &dp->dp_display; |
| |
| g_dp_display->enable = dp_display_enable; |
| g_dp_display->post_enable = dp_display_post_enable; |
| g_dp_display->pre_disable = dp_display_pre_disable; |
| g_dp_display->disable = dp_display_disable; |
| g_dp_display->set_mode = dp_display_set_mode; |
| g_dp_display->validate_mode = dp_display_validate_mode; |
| g_dp_display->get_modes = dp_display_get_modes; |
| g_dp_display->prepare = dp_display_prepare; |
| g_dp_display->unprepare = dp_display_unprepare; |
| g_dp_display->request_irq = dp_request_irq; |
| g_dp_display->get_debug = dp_get_debug; |
| g_dp_display->post_open = dp_display_post_open; |
| g_dp_display->post_init = dp_display_post_init; |
| g_dp_display->config_hdr = dp_display_config_hdr; |
| |
| rc = component_add(&pdev->dev, &dp_display_comp_ops); |
| if (rc) { |
| pr_err("component add failed, rc=%d\n", rc); |
| goto error; |
| } |
| |
| return 0; |
| error: |
| devm_kfree(&pdev->dev, dp); |
| bail: |
| return rc; |
| } |
| |
| int dp_display_get_displays(void **displays, int count) |
| { |
| if (!displays) { |
| pr_err("invalid data\n"); |
| return -EINVAL; |
| } |
| |
| if (count != 1) { |
| pr_err("invalid number of displays\n"); |
| return -EINVAL; |
| } |
| |
| displays[0] = g_dp_display; |
| return count; |
| } |
| |
| int dp_display_get_num_of_displays(void) |
| { |
| return 1; |
| } |
| |
| static int dp_display_remove(struct platform_device *pdev) |
| { |
| struct dp_display_private *dp; |
| |
| if (!pdev) |
| return -EINVAL; |
| |
| dp = platform_get_drvdata(pdev); |
| |
| dp_display_deinit_sub_modules(dp); |
| |
| platform_set_drvdata(pdev, NULL); |
| devm_kfree(&pdev->dev, dp); |
| |
| return 0; |
| } |
| |
| static struct platform_driver dp_display_driver = { |
| .probe = dp_display_probe, |
| .remove = dp_display_remove, |
| .driver = { |
| .name = "msm-dp-display", |
| .of_match_table = dp_dt_match, |
| }, |
| }; |
| |
| static int __init dp_display_init(void) |
| { |
| int ret; |
| |
| ret = platform_driver_register(&dp_display_driver); |
| if (ret) { |
| pr_err("driver register failed"); |
| return ret; |
| } |
| |
| return ret; |
| } |
| late_initcall(dp_display_init); |
| |
| static void __exit dp_display_cleanup(void) |
| { |
| platform_driver_unregister(&dp_display_driver); |
| } |
| module_exit(dp_display_cleanup); |
| |