summaryrefslogtreecommitdiff
path: root/drivers/usb/typec/tcpm/tcpci.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/typec/tcpm/tcpci.c')
-rw-r--r--drivers/usb/typec/tcpm/tcpci.c88
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, &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);
@@ -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, &reg);
regmap_read(tcpci->regmap, TCPC_POWER_STATUS_MASK, &reg);
/*
@@ -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, &reg);
+ 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,