summaryrefslogtreecommitdiff
path: root/drivers/usb/typec
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r--drivers/usb/typec/mux/Kconfig6
-rw-r--r--drivers/usb/typec/mux/Makefile1
-rw-r--r--drivers/usb/typec/mux/gpio-switch.c118
-rw-r--r--drivers/usb/typec/tcpm/tcpci.c88
-rw-r--r--drivers/usb/typec/tcpm/tcpm.c34
5 files changed, 228 insertions, 19 deletions
diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
index edead555835e..c66819e96046 100644
--- a/drivers/usb/typec/mux/Kconfig
+++ b/drivers/usb/typec/mux/Kconfig
@@ -19,4 +19,10 @@ config TYPEC_MUX_INTEL_PMC
control the USB role switch and also the multiplexer/demultiplexer
switches used with USB Type-C Alternate Modes.
+config TYPEC_SWITCH_GPIO
+ tristate "Simple Super Speed Active Switch via GPIO"
+ help
+ Say Y or M if your system has a typec super speed channel
+ switch via a simple GPIO control.
+
endmenu
diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile
index 280a6f553115..319069d30410 100644
--- a/drivers/usb/typec/mux/Makefile
+++ b/drivers/usb/typec/mux/Makefile
@@ -2,3 +2,4 @@
obj-$(CONFIG_TYPEC_MUX_PI3USB30532) += pi3usb30532.o
obj-$(CONFIG_TYPEC_MUX_INTEL_PMC) += intel_pmc_mux.o
+obj-$(CONFIG_TYPEC_SWITCH_GPIO) += gpio-switch.o
diff --git a/drivers/usb/typec/mux/gpio-switch.c b/drivers/usb/typec/mux/gpio-switch.c
new file mode 100644
index 000000000000..6029e224a848
--- /dev/null
+++ b/drivers/usb/typec/mux/gpio-switch.c
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * gpio-switch.c - typec switch via a simple GPIO control.
+ *
+ * Copyright 2019 NXP
+ * Author: Jun Li <jun.li@nxp.com>
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/usb/typec_mux.h>
+
+struct gpio_typec_switch {
+ struct typec_switch *sw;
+ struct mutex lock;
+ struct gpio_desc *ss_sel;
+ struct gpio_desc *ss_reset;
+};
+
+static int switch_gpio_set(struct typec_switch *sw,
+ enum typec_orientation orientation)
+{
+ struct gpio_typec_switch *gpio_sw = typec_switch_get_drvdata(sw);
+
+ mutex_lock(&gpio_sw->lock);
+
+ switch (orientation) {
+ case TYPEC_ORIENTATION_NORMAL:
+ gpiod_set_value_cansleep(gpio_sw->ss_sel, 1);
+ break;
+ case TYPEC_ORIENTATION_REVERSE:
+ gpiod_set_value_cansleep(gpio_sw->ss_sel, 0);
+ break;
+ case TYPEC_ORIENTATION_NONE:
+ break;
+ }
+
+ mutex_unlock(&gpio_sw->lock);
+
+ return 0;
+}
+
+static int typec_switch_gpio_probe(struct platform_device *pdev)
+{
+ struct gpio_typec_switch *gpio_sw;
+ struct device *dev = &pdev->dev;
+ struct typec_switch_desc sw_desc;
+
+ gpio_sw = devm_kzalloc(dev, sizeof(*gpio_sw), GFP_KERNEL);
+ if (!gpio_sw)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, gpio_sw);
+
+ sw_desc.drvdata = gpio_sw;
+ sw_desc.fwnode = dev->fwnode;
+ sw_desc.set = switch_gpio_set;
+ sw_desc.name = NULL;
+ mutex_init(&gpio_sw->lock);
+
+ /* Get the super speed mux reset GPIO, it's optional */
+ gpio_sw->ss_reset = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(gpio_sw->ss_reset))
+ return PTR_ERR(gpio_sw->ss_reset);
+
+ if (gpio_sw->ss_reset)
+ usleep_range(700, 1000);
+
+ /* Get the super speed active channel selection GPIO */
+ gpio_sw->ss_sel = devm_gpiod_get(dev, "switch", GPIOD_OUT_LOW);
+ if (IS_ERR(gpio_sw->ss_sel))
+ return PTR_ERR(gpio_sw->ss_sel);
+
+ gpio_sw->sw = typec_switch_register(dev, &sw_desc);
+ if (IS_ERR(gpio_sw->sw)) {
+ dev_err(dev, "Error registering typec switch: %ld\n", PTR_ERR(gpio_sw->sw));
+ return PTR_ERR(gpio_sw->sw);
+ }
+
+ return 0;
+}
+
+static int typec_switch_gpio_remove(struct platform_device *pdev)
+{
+ struct gpio_typec_switch *gpio_sw = platform_get_drvdata(pdev);
+
+ typec_switch_unregister(gpio_sw->sw);
+
+ return 0;
+}
+
+static const struct of_device_id of_typec_switch_gpio_match[] = {
+ { .compatible = "nxp,ptn36043" },
+ { .compatible = "nxp,cbtl04gp" },
+ { /* Sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, of_typec_switch_gpio_match);
+
+static struct platform_driver typec_switch_gpio_driver = {
+ .probe = typec_switch_gpio_probe,
+ .remove = typec_switch_gpio_remove,
+ .driver = {
+ .name = "typec-switch-gpio",
+ .of_match_table = of_typec_switch_gpio_match,
+ },
+};
+
+module_platform_driver(typec_switch_gpio_driver);
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("TypeC Super Speed Switch GPIO driver");
+MODULE_AUTHOR("Jun Li <jun.li@nxp.com>");
diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
index a7b0134d382b..a128df502d5f 100644
--- a/drivers/usb/typec/tcpm/tcpci.c
+++ b/drivers/usb/typec/tcpm/tcpci.c
@@ -10,6 +10,7 @@
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
+#include <linux/irq.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/usb/pd.h>
@@ -179,9 +180,6 @@ static int tcpci_start_toggling(struct tcpc_dev *tcpc,
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
unsigned int reg = TCPC_ROLE_CTRL_DRP;
- if (port_type != TYPEC_PORT_DRP)
- return -EOPNOTSUPP;
-
/* Handle vendor drp toggling */
if (tcpci->data->start_drp_toggling) {
ret = tcpci->data->start_drp_toggling(tcpci, tcpci->data, cc);
@@ -493,6 +491,32 @@ static bool tcpci_is_vbus_vsafe0v(struct tcpc_dev *tcpc)
return !!(reg & TCPC_EXTENDED_STATUS_VSAFE0V);
}
+static int tcpci_vbus_force_discharge(struct tcpc_dev *tcpc, bool enable)
+{
+ struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
+ unsigned int reg;
+ int ret;
+
+ if (enable)
+ regmap_write(tcpci->regmap,
+ TCPC_VBUS_VOLTAGE_ALARM_LO_CFG, 0x1c);
+ else
+ regmap_write(tcpci->regmap,
+ TCPC_VBUS_VOLTAGE_ALARM_LO_CFG, 0);
+
+ regmap_read(tcpci->regmap, TCPC_POWER_CTRL, &reg);
+
+ if (enable)
+ reg |= TCPC_POWER_CTRL_FORCEDISCH;
+ else
+ reg &= ~TCPC_POWER_CTRL_FORCEDISCH;
+ ret = regmap_write(tcpci->regmap, TCPC_POWER_CTRL, reg);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)
{
struct tcpci *tcpci = tcpc_to_tcpci(tcpc);
@@ -521,6 +545,9 @@ static int tcpci_set_vbus(struct tcpc_dev *tcpc, bool source, bool sink)
return ret;
}
+ if (!source && !sink)
+ tcpci_vbus_force_discharge(tcpc, true);
+
if (source) {
ret = regmap_write(tcpci->regmap, TCPC_COMMAND,
TCPC_CMD_SRC_VBUS_DEFAULT);
@@ -631,6 +658,9 @@ static int tcpci_init(struct tcpc_dev *tcpc)
if (ret < 0)
return ret;
+ /* Clear fault condition */
+ regmap_write(tcpci->regmap, TCPC_FAULT_STATUS, 0x80);
+
if (tcpci->controls_vbus)
reg = TCPC_POWER_STATUS_VBUS_PRES;
else
@@ -647,7 +677,8 @@ static int tcpci_init(struct tcpc_dev *tcpc)
reg = TCPC_ALERT_TX_SUCCESS | TCPC_ALERT_TX_FAILED |
TCPC_ALERT_TX_DISCARDED | TCPC_ALERT_RX_STATUS |
- TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS;
+ TCPC_ALERT_RX_HARD_RST | TCPC_ALERT_CC_STATUS |
+ TCPC_ALERT_V_ALARM_LO | TCPC_ALERT_FAULT;
if (tcpci->controls_vbus)
reg |= TCPC_ALERT_POWER_STATUS;
/* Enable VSAFE0V status interrupt when detecting VSAFE0V is supported */
@@ -681,7 +712,10 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci)
tcpm_cc_change(tcpci->port);
if (status & TCPC_ALERT_POWER_STATUS) {
+ /* Read power status to clear the event */
+ regmap_read(tcpci->regmap, TCPC_POWER_STATUS, &raw);
regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, &raw);
+
/*
* If power status mask has been reset, then the TCPC
* has reset.
@@ -692,6 +726,9 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci)
tcpm_vbus_change(tcpci->port);
}
+ if (status & TCPC_ALERT_V_ALARM_LO)
+ tcpci_vbus_force_discharge(&tcpci->tcpc, false);
+
if (status & TCPC_ALERT_RX_STATUS) {
struct pd_message msg;
unsigned int cnt, payload_cnt;
@@ -731,6 +768,13 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci)
tcpm_vbus_change(tcpci->port);
}
+ /* Clear the fault status anyway */
+ if (status & TCPC_ALERT_FAULT) {
+ regmap_read(tcpci->regmap, TCPC_FAULT_STATUS, &raw);
+ regmap_write(tcpci->regmap, TCPC_FAULT_STATUS,
+ raw | TCPC_FAULT_STATUS_CLEAR);
+ }
+
if (status & TCPC_ALERT_RX_HARD_RST)
tcpm_pd_hard_reset(tcpci->port);
@@ -863,15 +907,18 @@ static int tcpci_probe(struct i2c_client *client,
if (IS_ERR(chip->tcpci))
return PTR_ERR(chip->tcpci);
+ irq_set_status_flags(client->irq, IRQ_DISABLE_UNLAZY);
err = devm_request_threaded_irq(&client->dev, client->irq, NULL,
_tcpci_irq,
- IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+ IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_LOW,
dev_name(&client->dev), chip);
if (err < 0) {
tcpci_unregister_port(chip->tcpci);
return err;
}
+ device_set_wakeup_capable(chip->tcpci->dev, true);
+
return 0;
}
@@ -886,10 +933,40 @@ static int tcpci_remove(struct i2c_client *client)
dev_warn(&client->dev, "Failed to disable irqs (%pe)\n", ERR_PTR(err));
tcpci_unregister_port(chip->tcpci);
+ irq_clear_status_flags(client->irq, IRQ_DISABLE_UNLAZY);
+
+ return 0;
+}
+
+static int __maybe_unused tcpci_suspend(struct device *dev)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(i2c->irq);
+ else
+ disable_irq(i2c->irq);
return 0;
}
+
+static int __maybe_unused tcpci_resume(struct device *dev)
+{
+ struct i2c_client *i2c = to_i2c_client(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(i2c->irq);
+ else
+ enable_irq(i2c->irq);
+
+ return 0;
+}
+
+static const struct dev_pm_ops tcpci_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(tcpci_suspend, tcpci_resume)
+};
+
static const struct i2c_device_id tcpci_id[] = {
{ "tcpci", 0 },
{ }
@@ -907,6 +984,7 @@ MODULE_DEVICE_TABLE(of, tcpci_of_match);
static struct i2c_driver tcpci_i2c_driver = {
.driver = {
.name = "tcpci",
+ .pm = &tcpci_pm_ops,
.of_match_table = of_match_ptr(tcpci_of_match),
},
.probe = tcpci_probe,
diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
index 359c9bd7848f..12697238a553 100644
--- a/drivers/usb/typec/tcpm/tcpm.c
+++ b/drivers/usb/typec/tcpm/tcpm.c
@@ -3146,20 +3146,11 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
continue;
}
- switch (type) {
- case PDO_TYPE_FIXED:
- case PDO_TYPE_VAR:
+ if (type == PDO_TYPE_FIXED || type == PDO_TYPE_VAR) {
src_ma = pdo_max_current(pdo);
src_mw = src_ma * min_src_mv / 1000;
- break;
- case PDO_TYPE_BATT:
+ } else if (type == PDO_TYPE_BATT) {
src_mw = pdo_max_power(pdo);
- break;
- case PDO_TYPE_APDO:
- continue;
- default:
- tcpm_log(port, "Invalid source PDO type, ignoring");
- continue;
}
for (j = 0; j < port->nr_snk_pdo; j++) {
@@ -3603,6 +3594,8 @@ static int tcpm_src_attach(struct tcpm_port *port)
if (port->attached)
return 0;
+ tcpm_set_cc(port, tcpm_rp_cc(port));
+
ret = tcpm_set_polarity(port, polarity);
if (ret < 0)
return ret;
@@ -3750,6 +3743,8 @@ static int tcpm_snk_attach(struct tcpm_port *port)
if (port->attached)
return 0;
+ tcpm_set_cc(port, TYPEC_CC_RD);
+
ret = tcpm_set_polarity(port, port->cc2 != TYPEC_CC_OPEN ?
TYPEC_POLARITY_CC2 : TYPEC_POLARITY_CC1);
if (ret < 0)
@@ -4220,7 +4215,11 @@ static void run_state_machine(struct tcpm_port *port)
ret = tcpm_snk_attach(port);
if (ret < 0)
tcpm_set_state(port, SNK_UNATTACHED, 0);
- else
+ else if (port->port_type == TYPEC_PORT_SRC &&
+ port->typec_caps.data == TYPEC_PORT_DRD) {
+ tcpm_typec_connect(port);
+ tcpm_log(port, "Keep at SNK_ATTACHED for USB data.");
+ } else
tcpm_set_state(port, SNK_STARTUP, 0);
break;
case SNK_STARTUP:
@@ -6144,7 +6143,7 @@ static enum power_supply_property tcpm_psy_props[] = {
static int tcpm_psy_get_online(struct tcpm_port *port,
union power_supply_propval *val)
{
- if (port->vbus_charge) {
+ if (port->vbus_present && tcpm_port_is_sink(port)) {
if (port->pps_data.active)
val->intval = TCPM_PSY_PROG_ONLINE;
else
@@ -6267,7 +6266,7 @@ static int tcpm_psy_set_prop(struct power_supply *psy,
const union power_supply_propval *val)
{
struct tcpm_port *port = power_supply_get_drvdata(psy);
- int ret;
+ int ret = 0;
/*
* All the properties below are related to USB PD. The check needs to be
@@ -6293,6 +6292,9 @@ static int tcpm_psy_set_prop(struct power_supply *psy,
else
ret = tcpm_pps_set_op_curr(port, val->intval / 1000);
break;
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ port->usb_type = val->intval;
+ break;
default:
ret = -EINVAL;
break;
@@ -6315,6 +6317,10 @@ static int tcpm_psy_prop_writeable(struct power_supply *psy,
}
static enum power_supply_usb_type tcpm_psy_usb_types[] = {
+ POWER_SUPPLY_USB_TYPE_SDP,
+ POWER_SUPPLY_USB_TYPE_DCP,
+ POWER_SUPPLY_USB_TYPE_CDP,
+ POWER_SUPPLY_USB_TYPE_ACA,
POWER_SUPPLY_USB_TYPE_C,
POWER_SUPPLY_USB_TYPE_PD,
POWER_SUPPLY_USB_TYPE_PD_PPS,