diff options
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r-- | drivers/usb/typec/mux/Kconfig | 6 | ||||
-rw-r--r-- | drivers/usb/typec/mux/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/typec/mux/gpio-switch.c | 118 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpci.c | 88 | ||||
-rw-r--r-- | drivers/usb/typec/tcpm/tcpm.c | 34 |
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, ®); + + 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, |