diff --git a/drivers/usb/typec/tcpm/tcpci_husb311.c b/drivers/usb/typec/tcpm/tcpci_husb311.c index 185eee7f9729..d2c1591c3829 100644 --- a/drivers/usb/typec/tcpm/tcpci_husb311.c +++ b/drivers/usb/typec/tcpm/tcpci_husb311.c @@ -24,12 +24,18 @@ #define HUSB311_TCPC_TDRP 0xA2 #define HUSB311_TCPC_DCSRCDRP 0xA3 +#define HUSB311_PM_DELAY_S (3 * HZ) + struct husb311_chip { struct tcpci_data data; struct tcpci *tcpci; struct device *dev; struct regulator *vbus; + struct mutex lock; /* lock for sharing chip states */ + struct delayed_work pm_work; bool vbus_on; + bool charge_on; + bool suspended; }; static int husb311_read8(struct husb311_chip *chip, unsigned int reg, u8 *val) @@ -58,6 +64,49 @@ static struct husb311_chip *tdata_to_husb311(struct tcpci_data *tdata) return container_of(tdata, struct husb311_chip, data); } +static int husb311_disable_osc24m(struct husb311_chip *chip) +{ + int ret = 0; + u8 pwr; + + /* + * Disable 24M oscillator to save power consumption, and it will be + * enabled automatically when INT occurs. + */ + ret = husb311_read8(chip, HUSB311_TCPC_POWER, &pwr); + if (ret < 0) + return ret; + + pwr &= ~BIT(0); + ret = husb311_write8(chip, HUSB311_TCPC_POWER, pwr); + if (ret < 0) + return ret; + + return 0; +} + +static void husb311_pm_work(struct work_struct *work) +{ + struct husb311_chip *chip = + container_of(work, struct husb311_chip, pm_work.work); + + mutex_lock(&chip->lock); + + if (!chip->vbus_on && !chip->charge_on) { + if (chip->suspended) + goto exit; + + husb311_disable_osc24m(chip); + chip->suspended = 1; + } else { + if (chip->suspended) + chip->suspended = 0; + } + +exit: + mutex_unlock(&chip->lock); +} + static int husb311_sw_reset(struct husb311_chip *chip) { /* soft reset */ @@ -88,24 +137,35 @@ static int husb311_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, struct husb311_chip *chip = tdata_to_husb311(tdata); int ret = 0; + mutex_lock(&chip->lock); + if (chip->vbus_on == on) { dev_dbg(chip->dev, "vbus is already %s", on ? "On" : "Off"); - goto done; + } else { + if (on) + ret = regulator_enable(chip->vbus); + else + ret = regulator_disable(chip->vbus); + if (ret < 0) { + dev_err(chip->dev, "cannot %s vbus regulator, ret=%d", + on ? "enable" : "disable", ret); + goto done; + } + + chip->vbus_on = on; + dev_dbg(chip->dev, "vbus := %s", on ? "On" : "Off"); } - if (on) - ret = regulator_enable(chip->vbus); + if (chip->charge_on == charge) + dev_dbg(chip->dev, "charge is already %s", + charge ? "On" : "Off"); else - ret = regulator_disable(chip->vbus); - if (ret < 0) { - dev_err(chip->dev, "cannot %s vbus regulator, ret=%d", - on ? "enable" : "disable", ret); - goto done; - } + chip->charge_on = charge; - chip->vbus_on = on; + queue_delayed_work(system_freezable_wq, &chip->pm_work, HUSB311_PM_DELAY_S); done: + mutex_unlock(&chip->lock); return ret; } @@ -169,6 +229,9 @@ static int husb311_probe(struct i2c_client *client, chip->dev = &client->dev; i2c_set_clientdata(client, chip); + mutex_init(&chip->lock); + INIT_DELAYED_WORK(&chip->pm_work, husb311_pm_work); + chip->vbus = devm_regulator_get_optional(chip->dev, "vbus"); if (IS_ERR(chip->vbus)) { ret = PTR_ERR(chip->vbus); @@ -209,6 +272,7 @@ static void husb311_remove(struct i2c_client *client) struct husb311_chip *chip = i2c_get_clientdata(client); device_init_wakeup(chip->dev, false); + cancel_delayed_work_sync(&chip->pm_work); tcpci_unregister_port(chip->tcpci); } @@ -216,27 +280,17 @@ static int husb311_pm_suspend(struct device *dev) { struct husb311_chip *chip = dev->driver_data; struct i2c_client *client = to_i2c_client(dev); - int ret = 0; - u8 pwr; - - /* - * Disable 12M oscillator to save power consumption, and it will be - * enabled automatically when INT occur after system resume. - */ - ret = husb311_read8(chip, HUSB311_TCPC_POWER, &pwr); - if (ret < 0) - return ret; - - pwr &= ~BIT(0); - ret = husb311_write8(chip, HUSB311_TCPC_POWER, pwr); - if (ret < 0) - return ret; if (device_may_wakeup(dev) && !chip->vbus_on) enable_irq_wake(client->irq); else disable_irq(client->irq); + if (!chip->suspended) { + chip->suspended = 1; + husb311_disable_osc24m(chip); + } + return 0; }