summaryrefslogtreecommitdiff
path: root/drivers/usb/typec
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r--drivers/usb/typec/altmodes/displayport.c33
-rw-r--r--drivers/usb/typec/mux/intel_pmc_mux.c2
-rw-r--r--drivers/usb/typec/tcpm/fusb302.c20
-rw-r--r--drivers/usb/typec/tcpm/tcpci_maxim_core.c51
-rw-r--r--drivers/usb/typec/tcpm/tcpm.c34
-rw-r--r--drivers/usb/typec/ucsi/Kconfig2
-rw-r--r--drivers/usb/typec/ucsi/cros_ec_ucsi.c1
-rw-r--r--drivers/usb/typec/ucsi/psy.c2
-rw-r--r--drivers/usb/typec/ucsi/trace.c17
-rw-r--r--drivers/usb/typec/ucsi/trace.h1
-rw-r--r--drivers/usb/typec/ucsi/ucsi.c6
-rw-r--r--drivers/usb/typec/ucsi/ucsi.h11
-rw-r--r--drivers/usb/typec/ucsi/ucsi_ccg.c4
-rw-r--r--drivers/usb/typec/ucsi/ucsi_yoga_c630.c176
14 files changed, 282 insertions, 78 deletions
diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c
index b09b58d7311d..1dcb77faf85d 100644
--- a/drivers/usb/typec/altmodes/displayport.c
+++ b/drivers/usb/typec/altmodes/displayport.c
@@ -65,6 +65,13 @@ struct dp_altmode {
enum dp_state state;
bool hpd;
bool pending_hpd;
+ u32 irq_hpd_count;
+ /*
+ * hpd is mandatory for irq_hpd assertion, so irq_hpd also needs its own pending flag if
+ * both hpd and irq_hpd are asserted in the first Status Update before the pin assignment
+ * is configured.
+ */
+ bool pending_irq_hpd;
struct mutex lock; /* device lock */
struct work_struct work;
@@ -151,6 +158,7 @@ static int dp_altmode_status_update(struct dp_altmode *dp)
{
bool configured = !!DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
bool hpd = !!(dp->data.status & DP_STATUS_HPD_STATE);
+ bool irq_hpd = !!(dp->data.status & DP_STATUS_IRQ_HPD);
u8 con = DP_STATUS_CONNECTION(dp->data.status);
int ret = 0;
@@ -170,6 +178,8 @@ static int dp_altmode_status_update(struct dp_altmode *dp)
dp->hpd = hpd;
dp->pending_hpd = true;
}
+ if (dp->hpd && dp->pending_hpd && irq_hpd)
+ dp->pending_irq_hpd = true;
}
} else {
drm_connector_oob_hotplug_event(dp->connector_fwnode,
@@ -177,6 +187,10 @@ static int dp_altmode_status_update(struct dp_altmode *dp)
connector_status_disconnected);
dp->hpd = hpd;
sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
+ if (hpd && irq_hpd) {
+ dp->irq_hpd_count++;
+ sysfs_notify(&dp->alt->dev.kobj, "displayport", "irq_hpd");
+ }
}
return ret;
@@ -196,6 +210,11 @@ static int dp_altmode_configured(struct dp_altmode *dp)
connector_status_connected);
sysfs_notify(&dp->alt->dev.kobj, "displayport", "hpd");
dp->pending_hpd = false;
+ if (dp->pending_irq_hpd) {
+ dp->irq_hpd_count++;
+ sysfs_notify(&dp->alt->dev.kobj, "displayport", "irq_hpd");
+ dp->pending_irq_hpd = false;
+ }
}
return dp_altmode_notify(dp);
@@ -394,8 +413,7 @@ static int dp_altmode_vdm(struct typec_altmode *alt,
case CMDT_RSP_NAK:
switch (cmd) {
case DP_CMD_STATUS_UPDATE:
- if (typec_altmode_exit(alt))
- dev_err(&dp->alt->dev, "Exit Mode Failed!\n");
+ dp->state = DP_STATE_EXIT;
break;
case DP_CMD_CONFIGURE:
dp->data.conf = 0;
@@ -677,7 +695,7 @@ static ssize_t pin_assignment_show(struct device *dev,
assignments = get_current_pin_assignments(dp);
- for (i = 0; assignments; assignments >>= 1, i++) {
+ for (i = 0; assignments && i < DP_PIN_ASSIGN_MAX; assignments >>= 1, i++) {
if (assignments & 1) {
if (i == cur)
len += sprintf(buf + len, "[%s] ",
@@ -707,10 +725,19 @@ static ssize_t hpd_show(struct device *dev, struct device_attribute *attr, char
}
static DEVICE_ATTR_RO(hpd);
+static ssize_t irq_hpd_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct dp_altmode *dp = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", dp->irq_hpd_count);
+}
+static DEVICE_ATTR_RO(irq_hpd);
+
static struct attribute *displayport_attrs[] = {
&dev_attr_configuration.attr,
&dev_attr_pin_assignment.attr,
&dev_attr_hpd.attr,
+ &dev_attr_irq_hpd.attr,
NULL
};
diff --git a/drivers/usb/typec/mux/intel_pmc_mux.c b/drivers/usb/typec/mux/intel_pmc_mux.c
index 65dda9183e6f..1698428654ab 100644
--- a/drivers/usb/typec/mux/intel_pmc_mux.c
+++ b/drivers/usb/typec/mux/intel_pmc_mux.c
@@ -754,7 +754,7 @@ static int pmc_usb_probe(struct platform_device *pdev)
pmc->ipc = devm_intel_scu_ipc_dev_get(&pdev->dev);
if (!pmc->ipc)
- return -ENODEV;
+ return -EPROBE_DEFER;
pmc->dev = &pdev->dev;
diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c
index f15c63d3a8f4..a4ff2403ddd6 100644
--- a/drivers/usb/typec/tcpm/fusb302.c
+++ b/drivers/usb/typec/tcpm/fusb302.c
@@ -104,6 +104,7 @@ struct fusb302_chip {
bool vconn_on;
bool vbus_on;
bool charge_on;
+ bool pd_rx_on;
bool vbus_present;
enum typec_cc_polarity cc_polarity;
enum typec_cc_status cc1;
@@ -841,6 +842,11 @@ static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on)
int ret = 0;
mutex_lock(&chip->lock);
+ if (chip->pd_rx_on == on) {
+ fusb302_log(chip, "pd is already %s", str_on_off(on));
+ goto done;
+ }
+
ret = fusb302_pd_rx_flush(chip);
if (ret < 0) {
fusb302_log(chip, "cannot flush pd rx buffer, ret=%d", ret);
@@ -863,6 +869,8 @@ static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on)
str_on_off(on), ret);
goto done;
}
+
+ chip->pd_rx_on = on;
fusb302_log(chip, "pd := %s", str_on_off(on));
done:
mutex_unlock(&chip->lock);
@@ -1477,9 +1485,6 @@ static irqreturn_t fusb302_irq_intn(int irq, void *dev_id)
struct fusb302_chip *chip = dev_id;
unsigned long flags;
- /* Disable our level triggered IRQ until our irq_work has cleared it */
- disable_irq_nosync(chip->gpio_int_n_irq);
-
spin_lock_irqsave(&chip->irq_lock, flags);
if (chip->irq_suspended)
chip->irq_while_suspended = true;
@@ -1622,7 +1627,6 @@ static void fusb302_irq_work(struct work_struct *work)
}
done:
mutex_unlock(&chip->lock);
- enable_irq(chip->gpio_int_n_irq);
}
static int init_gpio(struct fusb302_chip *chip)
@@ -1747,9 +1751,10 @@ static int fusb302_probe(struct i2c_client *client)
goto destroy_workqueue;
}
- ret = request_irq(chip->gpio_int_n_irq, fusb302_irq_intn,
- IRQF_ONESHOT | IRQF_TRIGGER_LOW,
- "fsc_interrupt_int_n", chip);
+ ret = devm_request_threaded_irq(dev, chip->gpio_int_n_irq,
+ NULL, fusb302_irq_intn,
+ IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+ "fsc_interrupt_int_n", chip);
if (ret < 0) {
dev_err(dev, "cannot request IRQ for GPIO Int_N, ret=%d", ret);
goto tcpm_unregister_port;
@@ -1774,7 +1779,6 @@ static void fusb302_remove(struct i2c_client *client)
struct fusb302_chip *chip = i2c_get_clientdata(client);
disable_irq_wake(chip->gpio_int_n_irq);
- free_irq(chip->gpio_int_n_irq, chip);
cancel_work_sync(&chip->irq_work);
cancel_delayed_work_sync(&chip->bc_lvl_handler);
tcpm_unregister_port(chip->tcpm_port);
diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
index b5a5ed40faea..19f638650796 100644
--- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c
+++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c
@@ -421,21 +421,6 @@ static irqreturn_t max_tcpci_isr(int irq, void *dev_id)
return IRQ_WAKE_THREAD;
}
-static int max_tcpci_init_alert(struct max_tcpci_chip *chip, struct i2c_client *client)
-{
- int ret;
-
- ret = devm_request_threaded_irq(chip->dev, client->irq, max_tcpci_isr, max_tcpci_irq,
- (IRQF_TRIGGER_LOW | IRQF_ONESHOT), dev_name(chip->dev),
- chip);
-
- if (ret < 0)
- return ret;
-
- enable_irq_wake(client->irq);
- return 0;
-}
-
static int max_tcpci_start_toggling(struct tcpci *tcpci, struct tcpci_data *tdata,
enum typec_cc_status cc)
{
@@ -532,7 +517,9 @@ static int max_tcpci_probe(struct i2c_client *client)
chip->port = tcpci_get_tcpm_port(chip->tcpci);
- ret = max_tcpci_init_alert(chip, client);
+ ret = devm_request_threaded_irq(&client->dev, client->irq, max_tcpci_isr, max_tcpci_irq,
+ (IRQF_TRIGGER_LOW | IRQF_ONESHOT), dev_name(chip->dev),
+ chip);
if (ret < 0)
return dev_err_probe(&client->dev, ret,
"IRQ initialization failed\n");
@@ -544,24 +531,50 @@ static int max_tcpci_probe(struct i2c_client *client)
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int max_tcpci_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int ret = 0;
+
+ if (client->irq && device_may_wakeup(dev))
+ ret = disable_irq_wake(client->irq);
+
+ return ret;
+}
+
+static int max_tcpci_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ int ret = 0;
+
+ if (client->irq && device_may_wakeup(dev))
+ ret = enable_irq_wake(client->irq);
+
+ return ret;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static SIMPLE_DEV_PM_OPS(max_tcpci_pm_ops, max_tcpci_suspend, max_tcpci_resume);
+
static const struct i2c_device_id max_tcpci_id[] = {
{ "maxtcpc" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max_tcpci_id);
-#ifdef CONFIG_OF
static const struct of_device_id max_tcpci_of_match[] = {
{ .compatible = "maxim,max33359", },
{},
};
MODULE_DEVICE_TABLE(of, max_tcpci_of_match);
-#endif
static struct i2c_driver max_tcpci_i2c_driver = {
.driver = {
.name = "maxtcpc",
- .of_match_table = of_match_ptr(max_tcpci_of_match),
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
+ .of_match_table = max_tcpci_of_match,
+ .pm = &max_tcpci_pm_ops,
},
.probe = max_tcpci_probe,
.id_table = max_tcpci_id,
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 1a1f9e1f8e4e..1f6fdfaa34bf 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -4410,17 +4410,6 @@ static int tcpm_src_attach(struct tcpm_port *port)
tcpm_enable_auto_vbus_discharge(port, true);
- ret = tcpm_set_roles(port, true, TYPEC_STATE_USB,
- TYPEC_SOURCE, tcpm_data_role_for_source(port));
- if (ret < 0)
- return ret;
-
- if (port->pd_supported) {
- ret = port->tcpc->set_pd_rx(port->tcpc, true);
- if (ret < 0)
- goto out_disable_mux;
- }
-
/*
* USB Type-C specification, version 1.2,
* chapter 4.5.2.2.8.1 (Attached.SRC Requirements)
@@ -4430,13 +4419,24 @@ static int tcpm_src_attach(struct tcpm_port *port)
(polarity == TYPEC_POLARITY_CC2 && port->cc1 == TYPEC_CC_RA)) {
ret = tcpm_set_vconn(port, true);
if (ret < 0)
- goto out_disable_pd;
+ return ret;
}
ret = tcpm_set_vbus(port, true);
if (ret < 0)
goto out_disable_vconn;
+ ret = tcpm_set_roles(port, true, TYPEC_STATE_USB, TYPEC_SOURCE,
+ tcpm_data_role_for_source(port));
+ if (ret < 0)
+ goto out_disable_vbus;
+
+ if (port->pd_supported) {
+ ret = port->tcpc->set_pd_rx(port->tcpc, true);
+ if (ret < 0)
+ goto out_disable_mux;
+ }
+
port->pd_capable = false;
port->partner = NULL;
@@ -4447,14 +4447,14 @@ static int tcpm_src_attach(struct tcpm_port *port)
return 0;
-out_disable_vconn:
- tcpm_set_vconn(port, false);
-out_disable_pd:
- if (port->pd_supported)
- port->tcpc->set_pd_rx(port->tcpc, false);
out_disable_mux:
tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE,
TYPEC_ORIENTATION_NONE);
+out_disable_vbus:
+ tcpm_set_vbus(port, false);
+out_disable_vconn:
+ tcpm_set_vconn(port, false);
+
return ret;
}
diff --git a/drivers/usb/typec/ucsi/Kconfig b/drivers/usb/typec/ucsi/Kconfig
index 8bf8fefb4f07..7fcb1e1de5d6 100644
--- a/drivers/usb/typec/ucsi/Kconfig
+++ b/drivers/usb/typec/ucsi/Kconfig
@@ -85,6 +85,8 @@ config CROS_EC_UCSI
config UCSI_LENOVO_YOGA_C630
tristate "UCSI Interface Driver for Lenovo Yoga C630"
depends on EC_LENOVO_YOGA_C630
+ depends on DRM || !DRM
+ select DRM_AUX_HPD_BRIDGE if DRM_BRIDGE && OF
help
This driver enables UCSI support on the Lenovo Yoga C630 laptop.
diff --git a/drivers/usb/typec/ucsi/cros_ec_ucsi.c b/drivers/usb/typec/ucsi/cros_ec_ucsi.c
index 4ec1c6d22310..eed2a7d0ebc6 100644
--- a/drivers/usb/typec/ucsi/cros_ec_ucsi.c
+++ b/drivers/usb/typec/ucsi/cros_ec_ucsi.c
@@ -137,6 +137,7 @@ static int cros_ucsi_sync_control(struct ucsi *ucsi, u64 cmd, u32 *cci,
static const struct ucsi_operations cros_ucsi_ops = {
.read_version = cros_ucsi_read_version,
.read_cci = cros_ucsi_read_cci,
+ .poll_cci = cros_ucsi_read_cci,
.read_message_in = cros_ucsi_read_message_in,
.async_control = cros_ucsi_async_control,
.sync_control = cros_ucsi_sync_control,
diff --git a/drivers/usb/typec/ucsi/psy.c b/drivers/usb/typec/ucsi/psy.c
index 62ac69730405..62a9d68bb66d 100644
--- a/drivers/usb/typec/ucsi/psy.c
+++ b/drivers/usb/typec/ucsi/psy.c
@@ -164,7 +164,7 @@ static int ucsi_psy_get_current_max(struct ucsi_connector *con,
case UCSI_CONSTAT_PWR_OPMODE_DEFAULT:
/* UCSI can't tell b/w DCP/CDP or USB2/3x1/3x2 SDP chargers */
default:
- val->intval = 0;
+ val->intval = UCSI_TYPEC_DEFAULT_CURRENT * 1000;
break;
}
return 0;
diff --git a/drivers/usb/typec/ucsi/trace.c b/drivers/usb/typec/ucsi/trace.c
index 596a9542d401..13a38422743a 100644
--- a/drivers/usb/typec/ucsi/trace.c
+++ b/drivers/usb/typec/ucsi/trace.c
@@ -33,23 +33,6 @@ const char *ucsi_cmd_str(u64 raw_cmd)
return ucsi_cmd_strs[(cmd >= ARRAY_SIZE(ucsi_cmd_strs)) ? 0 : cmd];
}
-const char *ucsi_cci_str(u32 cci)
-{
- if (UCSI_CCI_CONNECTOR(cci)) {
- if (cci & UCSI_CCI_ACK_COMPLETE)
- return "Event pending (ACK completed)";
- if (cci & UCSI_CCI_COMMAND_COMPLETE)
- return "Event pending (command completed)";
- return "Connector Change";
- }
- if (cci & UCSI_CCI_ACK_COMPLETE)
- return "ACK completed";
- if (cci & UCSI_CCI_COMMAND_COMPLETE)
- return "Command completed";
-
- return "";
-}
-
static const char * const ucsi_recipient_strs[] = {
[UCSI_RECIPIENT_CON] = "port",
[UCSI_RECIPIENT_SOP] = "partner",
diff --git a/drivers/usb/typec/ucsi/trace.h b/drivers/usb/typec/ucsi/trace.h
index 41701dee7056..9554a0207276 100644
--- a/drivers/usb/typec/ucsi/trace.h
+++ b/drivers/usb/typec/ucsi/trace.h
@@ -10,7 +10,6 @@
#include <linux/usb/typec_altmode.h>
const char *ucsi_cmd_str(u64 raw_cmd);
-const char *ucsi_cci_str(u32 cci);
const char *ucsi_recipient_str(u8 recipient);
DECLARE_EVENT_CLASS(ucsi_log_command,
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index 01ce858a1a2b..5739ea2abdd1 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -531,13 +531,12 @@ ucsi_register_altmodes_nvidia(struct ucsi_connector *con, u8 recipient)
* Update the original altmode table as some ppms may report
* multiple DP altmodes.
*/
- if (recipient == UCSI_RECIPIENT_CON)
- multi_dp = ucsi->ops->update_altmodes(ucsi, orig, updated);
+ multi_dp = ucsi->ops->update_altmodes(ucsi, recipient, orig, updated);
/* now register altmodes */
for (i = 0; i < max_altmodes; i++) {
memset(&desc, 0, sizeof(desc));
- if (multi_dp && recipient == UCSI_RECIPIENT_CON) {
+ if (multi_dp) {
desc.svid = updated[i].svid;
desc.vdo = updated[i].mid;
} else {
@@ -1246,6 +1245,7 @@ static void ucsi_handle_connector_change(struct work_struct *work)
if (change & UCSI_CONSTAT_POWER_DIR_CHANGE) {
typec_set_pwr_role(con->port, role);
+ ucsi_port_psy_changed(con);
/* Complete pending power role swap */
if (!completion_done(&con->complete))
diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
index 5a8f947fcece..ebd7c27c2cc7 100644
--- a/drivers/usb/typec/ucsi/ucsi.h
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -50,6 +50,7 @@ struct dentry;
/* Command Status and Connector Change Indication (CCI) bits */
#define UCSI_CCI_CONNECTOR(_c_) (((_c_) & GENMASK(7, 1)) >> 1)
#define UCSI_CCI_LENGTH(_c_) (((_c_) & GENMASK(15, 8)) >> 8)
+#define UCSI_SET_CCI_LENGTH(_c_) ((_c_) << 8)
#define UCSI_CCI_NOT_SUPPORTED BIT(25)
#define UCSI_CCI_CANCEL_COMPLETE BIT(26)
#define UCSI_CCI_RESET_COMPLETE BIT(27)
@@ -82,7 +83,8 @@ struct ucsi_operations {
int (*sync_control)(struct ucsi *ucsi, u64 command, u32 *cci,
void *data, size_t size);
int (*async_control)(struct ucsi *ucsi, u64 command);
- bool (*update_altmodes)(struct ucsi *ucsi, struct ucsi_altmode *orig,
+ bool (*update_altmodes)(struct ucsi *ucsi, u8 recipient,
+ struct ucsi_altmode *orig,
struct ucsi_altmode *updated);
void (*update_connector)(struct ucsi_connector *con);
void (*connector_status)(struct ucsi_connector *con);
@@ -481,9 +483,10 @@ struct ucsi {
#define UCSI_MAX_SVID 5
#define UCSI_MAX_ALTMODES (UCSI_MAX_SVID * 6)
-#define UCSI_TYPEC_VSAFE5V 5000
-#define UCSI_TYPEC_1_5_CURRENT 1500
-#define UCSI_TYPEC_3_0_CURRENT 3000
+#define UCSI_TYPEC_VSAFE5V 5000
+#define UCSI_TYPEC_DEFAULT_CURRENT 100
+#define UCSI_TYPEC_1_5_CURRENT 1500
+#define UCSI_TYPEC_3_0_CURRENT 3000
struct ucsi_connector {
int num;
diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c
index e9a9df1431af..d83a0051c737 100644
--- a/drivers/usb/typec/ucsi/ucsi_ccg.c
+++ b/drivers/usb/typec/ucsi/ucsi_ccg.c
@@ -394,6 +394,7 @@ static void ucsi_ccg_update_get_current_cam_cmd(struct ucsi_ccg *uc, u8 *data)
}
static bool ucsi_ccg_update_altmodes(struct ucsi *ucsi,
+ u8 recipient,
struct ucsi_altmode *orig,
struct ucsi_altmode *updated)
{
@@ -402,6 +403,9 @@ static bool ucsi_ccg_update_altmodes(struct ucsi *ucsi,
int i, j, k = 0;
bool found = false;
+ if (recipient != UCSI_RECIPIENT_CON)
+ return false;
+
alt = uc->orig;
new_alt = uc->updated;
memset(uc->updated, 0, sizeof(uc->updated));
diff --git a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c
index d33e3f2dd1d8..0187c1c4b21a 100644
--- a/drivers/usb/typec/ucsi/ucsi_yoga_c630.c
+++ b/drivers/usb/typec/ucsi/ucsi_yoga_c630.c
@@ -7,18 +7,33 @@
*/
#include <linux/auxiliary_bus.h>
#include <linux/bitops.h>
+#include <linux/bitfield.h>
#include <linux/completion.h>
#include <linux/container_of.h>
#include <linux/module.h>
#include <linux/notifier.h>
+#include <linux/of.h>
+#include <linux/property.h>
#include <linux/string.h>
#include <linux/platform_data/lenovo-yoga-c630.h>
+#include <linux/usb/typec_dp.h>
+
+#include <drm/bridge/aux-bridge.h>
#include "ucsi.h"
+#define LENOVO_EC_USB_MUX 0x08
+
+#define USB_MUX_MUXC GENMASK(1, 0)
+#define USB_MUX_CCST GENMASK(3, 2)
+#define USB_MUX_DPPN GENMASK(7, 4)
+#define USB_MUX_HPDS BIT(8)
+#define USB_MUX_HSFL GENMASK(11, 9)
+
struct yoga_c630_ucsi {
struct yoga_c630_ec *ec;
struct ucsi *ucsi;
+ struct auxiliary_device *bridge;
struct notifier_block nb;
u16 version;
};
@@ -71,15 +86,127 @@ static int yoga_c630_ucsi_async_control(struct ucsi *ucsi, u64 command)
return yoga_c630_ec_ucsi_write(uec->ec, (u8*)&command);
}
+static int yoga_c630_ucsi_sync_control(struct ucsi *ucsi,
+ u64 command,
+ u32 *cci,
+ void *data, size_t size)
+{
+ int ret;
+
+ /*
+ * EC doesn't return connector's DP mode even though it is supported.
+ * Fake it.
+ */
+ if (UCSI_COMMAND(command) == UCSI_GET_ALTERNATE_MODES &&
+ UCSI_GET_ALTMODE_GET_CONNECTOR_NUMBER(command) == 1 &&
+ UCSI_ALTMODE_RECIPIENT(command) == UCSI_RECIPIENT_CON &&
+ UCSI_ALTMODE_OFFSET(command) == 0) {
+ static const struct ucsi_altmode alt = {
+ .svid = USB_TYPEC_DP_SID,
+ .mid = USB_TYPEC_DP_MODE,
+ };
+
+ dev_dbg(ucsi->dev, "faking DP altmode for con1\n");
+ memset(data, 0, size);
+ memcpy(data, &alt, min(sizeof(alt), size));
+ *cci = UCSI_CCI_COMMAND_COMPLETE | UCSI_SET_CCI_LENGTH(sizeof(alt));
+ return 0;
+ }
+
+ /*
+ * EC can return AltModes present on CON1 (port0, right) for CON2
+ * (port1, left) too. Ignore all requests going to CON2 (it doesn't
+ * support DP anyway).
+ */
+ if (UCSI_COMMAND(command) == UCSI_GET_ALTERNATE_MODES &&
+ UCSI_GET_ALTMODE_GET_CONNECTOR_NUMBER(command) == 2) {
+ dev_dbg(ucsi->dev, "ignoring altmodes for con2\n");
+ memset(data, 0, size);
+ *cci = UCSI_CCI_COMMAND_COMPLETE;
+ return 0;
+ }
+
+ ret = ucsi_sync_control_common(ucsi, command, cci, data, size);
+ if (ret < 0)
+ return ret;
+
+ /* UCSI_GET_CURRENT_CAM is off-by-one on all ports */
+ if (UCSI_COMMAND(command) == UCSI_GET_CURRENT_CAM && data)
+ ((u8 *)data)[0]--;
+
+ return ret;
+}
+
+static bool yoga_c630_ucsi_update_altmodes(struct ucsi *ucsi,
+ u8 recipient,
+ struct ucsi_altmode *orig,
+ struct ucsi_altmode *updated)
+{
+ int i;
+
+ if (orig[0].svid == 0 || recipient != UCSI_RECIPIENT_SOP)
+ return false;
+
+ /* EC is nice and repeats altmodes again and again. Ignore copies. */
+ for (i = 1; i < UCSI_MAX_ALTMODES; i++) {
+ if (orig[i].svid == orig[0].svid) {
+ dev_dbg(ucsi->dev, "Found duplicate altmodes, starting from %d\n", i);
+ memset(&orig[i], 0, (UCSI_MAX_ALTMODES - i) * sizeof(*orig));
+ break;
+ }
+ }
+
+ return false;
+}
+
+static void yoga_c630_ucsi_update_connector(struct ucsi_connector *con)
+{
+ if (con->num == 1)
+ con->typec_cap.orientation_aware = true;
+}
+
static const struct ucsi_operations yoga_c630_ucsi_ops = {
.read_version = yoga_c630_ucsi_read_version,
.read_cci = yoga_c630_ucsi_read_cci,
.poll_cci = yoga_c630_ucsi_read_cci,
.read_message_in = yoga_c630_ucsi_read_message_in,
- .sync_control = ucsi_sync_control_common,
+ .sync_control = yoga_c630_ucsi_sync_control,
.async_control = yoga_c630_ucsi_async_control,
+ .update_altmodes = yoga_c630_ucsi_update_altmodes,
+ .update_connector = yoga_c630_ucsi_update_connector,
};
+static void yoga_c630_ucsi_read_port0_status(struct yoga_c630_ucsi *uec)
+{
+ int val;
+ unsigned int muxc, ccst, dppn, hpds, hsfl;
+
+ val = yoga_c630_ec_read16(uec->ec, LENOVO_EC_USB_MUX);
+
+ muxc = FIELD_GET(USB_MUX_MUXC, val);
+ ccst = FIELD_GET(USB_MUX_CCST, val);
+ dppn = FIELD_GET(USB_MUX_DPPN, val);
+ hpds = FIELD_GET(USB_MUX_HPDS, val);
+ hsfl = FIELD_GET(USB_MUX_HSFL, val);
+
+ dev_dbg(uec->ucsi->dev, " mux %04x (muxc %d ccst %d dppn %d hpds %d hsfl %d)\n",
+ val,
+ muxc, ccst, dppn, hpds, hsfl);
+
+ if (uec->ucsi->connector && uec->ucsi->connector[0].port)
+ typec_set_orientation(uec->ucsi->connector[0].port,
+ ccst == 1 ?
+ TYPEC_ORIENTATION_REVERSE :
+ TYPEC_ORIENTATION_NORMAL);
+
+ if (uec->bridge)
+ drm_aux_hpd_bridge_notify(&uec->bridge->dev,
+ dppn != 0 ?
+ connector_status_connected :
+ connector_status_disconnected);
+
+}
+
static int yoga_c630_ucsi_notify(struct notifier_block *nb,
unsigned long action, void *data)
{
@@ -90,6 +217,7 @@ static int yoga_c630_ucsi_notify(struct notifier_block *nb,
switch (action) {
case LENOVO_EC_EVENT_USB:
case LENOVO_EC_EVENT_HPD:
+ yoga_c630_ucsi_read_port0_status(uec);
ucsi_connector_change(uec->ucsi, 1);
return NOTIFY_OK;
@@ -121,6 +249,24 @@ static int yoga_c630_ucsi_probe(struct auxiliary_device *adev,
uec->ec = ec;
uec->nb.notifier_call = yoga_c630_ucsi_notify;
+ device_for_each_child_node_scoped(&adev->dev, fwnode) {
+ u32 port;
+
+ ret = fwnode_property_read_u32(fwnode, "reg", &port);
+ if (ret < 0) {
+ dev_err(&adev->dev, "missing reg property of %pfwP\n", fwnode);
+ return ret;
+ }
+
+ /* DP is only on port0 */
+ if (port != 0)
+ continue;
+
+ uec->bridge = devm_drm_dp_hpd_bridge_alloc(&adev->dev, to_of_node(fwnode));
+ if (IS_ERR(uec->bridge))
+ return PTR_ERR(uec->bridge);
+ }
+
uec->ucsi = ucsi_create(&adev->dev, &yoga_c630_ucsi_ops);
if (IS_ERR(uec->ucsi))
return PTR_ERR(uec->ucsi);
@@ -133,17 +279,39 @@ static int yoga_c630_ucsi_probe(struct auxiliary_device *adev,
ret = yoga_c630_ec_register_notify(ec, &uec->nb);
if (ret)
- return ret;
+ goto err_destroy;
+
+ ret = ucsi_register(uec->ucsi);
+ if (ret)
+ goto err_unregister;
+
+ if (uec->bridge) {
+ ret = devm_drm_dp_hpd_bridge_add(&adev->dev, uec->bridge);
+ if (ret)
+ goto err_ucsi_unregister;
+ }
- return ucsi_register(uec->ucsi);
+ return 0;
+
+err_ucsi_unregister:
+ ucsi_unregister(uec->ucsi);
+
+err_unregister:
+ yoga_c630_ec_unregister_notify(uec->ec, &uec->nb);
+
+err_destroy:
+ ucsi_destroy(uec->ucsi);
+
+ return ret;
}
static void yoga_c630_ucsi_remove(struct auxiliary_device *adev)
{
struct yoga_c630_ucsi *uec = auxiliary_get_drvdata(adev);
- yoga_c630_ec_unregister_notify(uec->ec, &uec->nb);
ucsi_unregister(uec->ucsi);
+ yoga_c630_ec_unregister_notify(uec->ec, &uec->nb);
+ ucsi_destroy(uec->ucsi);
}
static const struct auxiliary_device_id yoga_c630_ucsi_id_table[] = {