diff options
Diffstat (limited to 'drivers/usb/typec/tcpm/tcpci.c')
-rw-r--r-- | drivers/usb/typec/tcpm/tcpci.c | 88 |
1 files changed, 82 insertions, 6 deletions
diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c index 59e304a341f8..ffef680fbd62 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> @@ -117,9 +118,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); @@ -289,6 +287,32 @@ static int tcpci_get_vbus(struct tcpc_dev *tcpc) return !!(reg & TCPC_POWER_STATUS_VBUS_PRES); } +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); @@ -310,6 +334,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); @@ -391,6 +418,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 @@ -407,7 +437,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; return tcpci_write16(tcpci, TCPC_ALERT_MASK, reg); @@ -416,6 +447,7 @@ static int tcpci_init(struct tcpc_dev *tcpc) irqreturn_t tcpci_irq(struct tcpci *tcpci) { u16 status; + unsigned int reg; tcpci_read16(tcpci, TCPC_ALERT, &status); @@ -431,8 +463,8 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci) tcpm_cc_change(tcpci->port); if (status & TCPC_ALERT_POWER_STATUS) { - unsigned int reg; - + /* Read power status to clear the event */ + regmap_read(tcpci->regmap, TCPC_POWER_STATUS, ®); regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, ®); /* @@ -445,6 +477,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; @@ -478,6 +513,13 @@ irqreturn_t tcpci_irq(struct tcpci *tcpci) tcpm_pd_receive(tcpci->port, &msg); } + /* Clear the fault status anyway */ + if (status & TCPC_ALERT_FAULT) { + regmap_read(tcpci->regmap, TCPC_FAULT_STATUS, ®); + regmap_write(tcpci->regmap, TCPC_FAULT_STATUS, + reg | TCPC_FAULT_STATUS_CLEAR); + } + if (status & TCPC_ALERT_RX_HARD_RST) tcpm_pd_hard_reset(tcpci->port); @@ -591,6 +633,7 @@ 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, @@ -600,6 +643,8 @@ static int tcpci_probe(struct i2c_client *client, return err; } + device_set_wakeup_capable(chip->tcpci->dev, true); + return 0; } @@ -614,10 +659,40 @@ static int tcpci_remove(struct i2c_client *client) return 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 }, { } @@ -635,6 +710,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, |