
Originally, a call is made to dwc3_msm_release_ss_lane to notify the USB driver that DP 4 lane is in use. This was cleared on USB disconnect. Combined with the autosuspend timeout and UCSI notifying that USB was not needed, this caused issues in bringing up the monitors. This change implements a new api call from the USB driver, dwc3_msm_set_dp_mode. DP driver makes a call to this api to notify USB driver for 2 lane, 4 lane, and disconnected states. Change-Id: I051e5424c518ca84e2cc6dc27c114715dc287ac0 Signed-off-by: Sudarsan Ramesh <sudarame@codeaurora.org>
323 lines
7.6 KiB
C
323 lines
7.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2012-2021, The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/device.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/soc/qcom/altmode-glink.h>
|
|
#include <linux/usb/dwc3-msm.h>
|
|
#include <linux/usb/pd_vdo.h>
|
|
|
|
#include "dp_altmode.h"
|
|
#include "dp_debug.h"
|
|
#include "sde_dbg.h"
|
|
|
|
|
|
#define ALTMODE_CONFIGURE_MASK (0x3f)
|
|
#define ALTMODE_HPD_STATE_MASK (0x40)
|
|
#define ALTMODE_HPD_IRQ_MASK (0x80)
|
|
|
|
struct dp_altmode_private {
|
|
bool forced_disconnect;
|
|
struct device *dev;
|
|
struct dp_hpd_cb *dp_cb;
|
|
struct dp_altmode dp_altmode;
|
|
struct altmode_client *amclient;
|
|
bool connected;
|
|
};
|
|
|
|
enum dp_altmode_pin_assignment {
|
|
DPAM_HPD_OUT,
|
|
DPAM_HPD_A,
|
|
DPAM_HPD_B,
|
|
DPAM_HPD_C,
|
|
DPAM_HPD_D,
|
|
DPAM_HPD_E,
|
|
DPAM_HPD_F,
|
|
};
|
|
|
|
static int dp_altmode_set_usb_dp_mode(struct dp_altmode_private *altmode)
|
|
{
|
|
int rc;
|
|
struct device_node *np;
|
|
struct device_node *usb_node;
|
|
struct platform_device *usb_pdev;
|
|
int timeout = 250;
|
|
u32 lanes = 4;
|
|
|
|
if (!altmode || !altmode->dev) {
|
|
DP_ERR("invalid args\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
np = altmode->dev->of_node;
|
|
|
|
if (altmode->connected && altmode->dp_altmode.base.force_multi_func)
|
|
lanes = 2;
|
|
|
|
usb_node = of_parse_phandle(np, "usb-controller", 0);
|
|
if (!usb_node) {
|
|
DP_ERR("unable to get usb node\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
usb_pdev = of_find_device_by_node(usb_node);
|
|
if (!usb_pdev) {
|
|
of_node_put(usb_node);
|
|
DP_ERR("unable to get usb pdev\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
while (timeout) {
|
|
rc = dwc3_msm_set_dp_mode(&usb_pdev->dev, altmode->connected, lanes);
|
|
if (rc != -EBUSY)
|
|
break;
|
|
|
|
DP_WARN("USB busy, retry\n");
|
|
|
|
/* wait for hw recommended delay for usb */
|
|
msleep(20);
|
|
timeout--;
|
|
}
|
|
of_node_put(usb_node);
|
|
platform_device_put(usb_pdev);
|
|
|
|
if (rc)
|
|
DP_ERR("Error releasing SS lanes: %d\n", rc);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void dp_altmode_send_pan_ack(struct altmode_client *amclient,
|
|
u8 port_index)
|
|
{
|
|
int rc;
|
|
struct altmode_pan_ack_msg ack;
|
|
|
|
ack.cmd_type = ALTMODE_PAN_ACK;
|
|
ack.port_index = port_index;
|
|
|
|
rc = altmode_send_data(amclient, &ack, sizeof(ack));
|
|
if (rc < 0) {
|
|
DP_ERR("failed: %d\n", rc);
|
|
return;
|
|
}
|
|
|
|
DP_DEBUG("port=%d\n", port_index);
|
|
}
|
|
|
|
static int dp_altmode_notify(void *priv, void *data, size_t len)
|
|
{
|
|
int rc = 0;
|
|
struct dp_altmode_private *altmode =
|
|
(struct dp_altmode_private *) priv;
|
|
u8 port_index, dp_data, orientation;
|
|
u8 *payload = (u8 *) data;
|
|
u8 pin, hpd_state, hpd_irq;
|
|
bool force_multi_func = altmode->dp_altmode.base.force_multi_func;
|
|
|
|
port_index = payload[0];
|
|
orientation = payload[1];
|
|
dp_data = payload[8];
|
|
|
|
pin = dp_data & ALTMODE_CONFIGURE_MASK;
|
|
hpd_state = (dp_data & ALTMODE_HPD_STATE_MASK) >> 6;
|
|
hpd_irq = (dp_data & ALTMODE_HPD_IRQ_MASK) >> 7;
|
|
|
|
altmode->dp_altmode.base.hpd_high = !!hpd_state;
|
|
altmode->dp_altmode.base.hpd_irq = !!hpd_irq;
|
|
altmode->dp_altmode.base.multi_func = force_multi_func ? true :
|
|
!(pin == DPAM_HPD_C || pin == DPAM_HPD_E || pin == DPAM_HPD_OUT);
|
|
|
|
DP_DEBUG("payload=0x%x\n", dp_data);
|
|
DP_DEBUG("port_index=%d, orientation=%d, pin=%d, hpd_state=%d\n",
|
|
port_index, orientation, pin, hpd_state);
|
|
DP_DEBUG("multi_func=%d, hpd_high=%d, hpd_irq=%d\n",
|
|
altmode->dp_altmode.base.multi_func,
|
|
altmode->dp_altmode.base.hpd_high,
|
|
altmode->dp_altmode.base.hpd_irq);
|
|
DP_DEBUG("connected=%d\n", altmode->connected);
|
|
SDE_EVT32_EXTERNAL(dp_data, port_index, orientation, pin, hpd_state,
|
|
altmode->dp_altmode.base.multi_func,
|
|
altmode->dp_altmode.base.hpd_high,
|
|
altmode->dp_altmode.base.hpd_irq, altmode->connected);
|
|
|
|
if (!pin) {
|
|
/* Cable detach */
|
|
if (altmode->connected) {
|
|
altmode->connected = false;
|
|
altmode->dp_altmode.base.alt_mode_cfg_done = false;
|
|
altmode->dp_altmode.base.orientation = ORIENTATION_NONE;
|
|
if (altmode->dp_cb && altmode->dp_cb->disconnect)
|
|
altmode->dp_cb->disconnect(altmode->dev);
|
|
|
|
rc = dp_altmode_set_usb_dp_mode(altmode);
|
|
if (rc)
|
|
DP_ERR("failed to clear usb dp mode, rc: %d\n", rc);
|
|
}
|
|
goto ack;
|
|
}
|
|
|
|
/* Configure */
|
|
if (!altmode->connected) {
|
|
altmode->connected = true;
|
|
altmode->dp_altmode.base.alt_mode_cfg_done = true;
|
|
altmode->forced_disconnect = false;
|
|
|
|
switch (orientation) {
|
|
case 0:
|
|
orientation = ORIENTATION_CC1;
|
|
break;
|
|
case 1:
|
|
orientation = ORIENTATION_CC2;
|
|
break;
|
|
case 2:
|
|
orientation = ORIENTATION_NONE;
|
|
break;
|
|
default:
|
|
orientation = ORIENTATION_NONE;
|
|
break;
|
|
}
|
|
|
|
altmode->dp_altmode.base.orientation = orientation;
|
|
|
|
rc = dp_altmode_set_usb_dp_mode(altmode);
|
|
if (rc)
|
|
goto ack;
|
|
|
|
if (altmode->dp_cb && altmode->dp_cb->configure)
|
|
altmode->dp_cb->configure(altmode->dev);
|
|
goto ack;
|
|
}
|
|
|
|
/* Attention */
|
|
if (altmode->forced_disconnect)
|
|
goto ack;
|
|
|
|
if (altmode->dp_cb && altmode->dp_cb->attention)
|
|
altmode->dp_cb->attention(altmode->dev);
|
|
ack:
|
|
dp_altmode_send_pan_ack(altmode->amclient, port_index);
|
|
return rc;
|
|
}
|
|
|
|
static void dp_altmode_register(void *priv)
|
|
{
|
|
struct dp_altmode_private *altmode = priv;
|
|
struct altmode_client_data cd = {
|
|
.callback = &dp_altmode_notify,
|
|
};
|
|
|
|
cd.name = "displayport";
|
|
cd.svid = USB_SID_DISPLAYPORT;
|
|
cd.priv = altmode;
|
|
|
|
altmode->amclient = altmode_register_client(altmode->dev, &cd);
|
|
if (IS_ERR_OR_NULL(altmode->amclient))
|
|
DP_ERR("failed to register as client: %ld\n",
|
|
PTR_ERR(altmode->amclient));
|
|
else
|
|
DP_DEBUG("success\n");
|
|
}
|
|
|
|
static int dp_altmode_simulate_connect(struct dp_hpd *dp_hpd, bool hpd)
|
|
{
|
|
struct dp_altmode *dp_altmode;
|
|
struct dp_altmode_private *altmode;
|
|
|
|
dp_altmode = container_of(dp_hpd, struct dp_altmode, base);
|
|
altmode = container_of(dp_altmode, struct dp_altmode_private,
|
|
dp_altmode);
|
|
|
|
dp_altmode->base.hpd_high = hpd;
|
|
altmode->forced_disconnect = !hpd;
|
|
altmode->dp_altmode.base.alt_mode_cfg_done = hpd;
|
|
|
|
if (hpd)
|
|
altmode->dp_cb->configure(altmode->dev);
|
|
else
|
|
altmode->dp_cb->disconnect(altmode->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dp_altmode_simulate_attention(struct dp_hpd *dp_hpd, int vdo)
|
|
{
|
|
struct dp_altmode *dp_altmode;
|
|
struct dp_altmode_private *altmode;
|
|
struct dp_altmode *status;
|
|
|
|
dp_altmode = container_of(dp_hpd, struct dp_altmode, base);
|
|
altmode = container_of(dp_altmode, struct dp_altmode_private,
|
|
dp_altmode);
|
|
|
|
status = &altmode->dp_altmode;
|
|
|
|
status->base.hpd_high = (vdo & BIT(7)) ? true : false;
|
|
status->base.hpd_irq = (vdo & BIT(8)) ? true : false;
|
|
|
|
if (altmode->dp_cb && altmode->dp_cb->attention)
|
|
altmode->dp_cb->attention(altmode->dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct dp_hpd *dp_altmode_get(struct device *dev, struct dp_hpd_cb *cb)
|
|
{
|
|
int rc = 0;
|
|
struct dp_altmode_private *altmode;
|
|
struct dp_altmode *dp_altmode;
|
|
|
|
if (!cb) {
|
|
DP_ERR("invalid cb data\n");
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
altmode = kzalloc(sizeof(*altmode), GFP_KERNEL);
|
|
if (!altmode)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
altmode->dev = dev;
|
|
altmode->dp_cb = cb;
|
|
|
|
dp_altmode = &altmode->dp_altmode;
|
|
dp_altmode->base.register_hpd = NULL;
|
|
dp_altmode->base.simulate_connect = dp_altmode_simulate_connect;
|
|
dp_altmode->base.simulate_attention = dp_altmode_simulate_attention;
|
|
|
|
rc = altmode_register_notifier(dev, dp_altmode_register, altmode);
|
|
if (rc < 0) {
|
|
DP_ERR("altmode probe notifier registration failed: %d\n", rc);
|
|
goto error;
|
|
}
|
|
|
|
DP_DEBUG("success\n");
|
|
|
|
return &dp_altmode->base;
|
|
error:
|
|
kfree(altmode);
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
void dp_altmode_put(struct dp_hpd *dp_hpd)
|
|
{
|
|
struct dp_altmode *dp_altmode;
|
|
struct dp_altmode_private *altmode;
|
|
|
|
dp_altmode = container_of(dp_hpd, struct dp_altmode, base);
|
|
if (!dp_altmode)
|
|
return;
|
|
|
|
altmode = container_of(dp_altmode, struct dp_altmode_private,
|
|
dp_altmode);
|
|
|
|
altmode_deregister_client(altmode->amclient);
|
|
altmode_deregister_notifier(altmode->dev, altmode);
|
|
|
|
kfree(altmode);
|
|
}
|