Skip to content

Commit

Permalink
USB: TWL: disable VUSB regulators when cable unplugged
Browse files Browse the repository at this point in the history
This patch disables USB regulators VUSB1V5, VUSB1V8, and VUSB3V1
when the USB cable is unplugged to reduce power consumption.
Added a depencency from twl4030 usb driver to TWL_REGULATOR.

Signed-off-by: Jouni Hogander <jouni.hogander@nokia.com>
Signed-off-by: Kalle Jokiniemi <kalle.jokiniemi@digia.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
  • Loading branch information
Jouni Hogander authored and Greg Kroah-Hartman committed Mar 24, 2009
1 parent 471c604 commit 6676016
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 10 deletions.
2 changes: 1 addition & 1 deletion drivers/usb/otg/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ config ISP1301_OMAP

config TWL4030_USB
tristate "TWL4030 USB Transceiver Driver"
depends on TWL4030_CORE
depends on TWL4030_CORE && REGULATOR_TWL4030
select USB_OTG_UTILS
help
Enable this to support the USB OTG transceiver on TWL4030
Expand Down
73 changes: 64 additions & 9 deletions drivers/usb/otg/twl4030-usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
#include <linux/delay.h>
#include <linux/usb/otg.h>
#include <linux/i2c/twl4030.h>
#include <linux/regulator/consumer.h>
#include <linux/err.h>


/* Register defines */
Expand Down Expand Up @@ -246,6 +248,11 @@ struct twl4030_usb {
struct otg_transceiver otg;
struct device *dev;

/* TWL4030 internal USB regulator supplies */
struct regulator *usb1v5;
struct regulator *usb1v8;
struct regulator *usb3v1;

/* for vbus reporting with irqs disabled */
spinlock_t lock;

Expand Down Expand Up @@ -434,6 +441,18 @@ static void twl4030_phy_power(struct twl4030_usb *twl, int on)

pwr = twl4030_usb_read(twl, PHY_PWR_CTRL);
if (on) {
regulator_enable(twl->usb3v1);
regulator_enable(twl->usb1v8);
/*
* Disabling usb3v1 regulator (= writing 0 to VUSB3V1_DEV_GRP
* in twl4030) resets the VUSB_DEDICATED2 register. This reset
* enables VUSB3V1_SLEEP bit that remaps usb3v1 ACTIVE state to
* SLEEP. We work around this by clearing the bit after usv3v1
* is re-activated. This ensures that VUSB3V1 is really active.
*/
twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0,
VUSB_DEDICATED2);
regulator_enable(twl->usb1v5);
pwr &= ~PHY_PWR_PHYPWD;
WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0);
twl4030_usb_write(twl, PHY_CLK_CTRL,
Expand All @@ -443,6 +462,9 @@ static void twl4030_phy_power(struct twl4030_usb *twl, int on)
} else {
pwr |= PHY_PWR_PHYPWD;
WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0);
regulator_disable(twl->usb1v5);
regulator_disable(twl->usb1v8);
regulator_disable(twl->usb3v1);
}
}

Expand All @@ -468,7 +490,7 @@ static void twl4030_phy_resume(struct twl4030_usb *twl)
twl->asleep = 0;
}

static void twl4030_usb_ldo_init(struct twl4030_usb *twl)
static int twl4030_usb_ldo_init(struct twl4030_usb *twl)
{
/* Enable writing to power configuration registers */
twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0xC0, PROTECT_KEY);
Expand All @@ -480,20 +502,45 @@ static void twl4030_usb_ldo_init(struct twl4030_usb *twl)
/* input to VUSB3V1 LDO is from VBAT, not VBUS */
twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1);

/* turn on 3.1V regulator */
twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x20, VUSB3V1_DEV_GRP);
/* Initialize 3.1V regulator */
twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB3V1_DEV_GRP);

twl->usb3v1 = regulator_get(twl->dev, "usb3v1");
if (IS_ERR(twl->usb3v1))
return -ENODEV;

twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB3V1_TYPE);

/* turn on 1.5V regulator */
twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x20, VUSB1V5_DEV_GRP);
/* Initialize 1.5V regulator */
twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB1V5_DEV_GRP);

twl->usb1v5 = regulator_get(twl->dev, "usb1v5");
if (IS_ERR(twl->usb1v5))
goto fail1;

twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB1V5_TYPE);

/* turn on 1.8V regulator */
twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x20, VUSB1V8_DEV_GRP);
/* Initialize 1.8V regulator */
twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB1V8_DEV_GRP);

twl->usb1v8 = regulator_get(twl->dev, "usb1v8");
if (IS_ERR(twl->usb1v8))
goto fail2;

twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB1V8_TYPE);

/* disable access to power configuration registers */
twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0, PROTECT_KEY);

return 0;

fail2:
regulator_put(twl->usb1v5);
twl->usb1v5 = NULL;
fail1:
regulator_put(twl->usb3v1);
twl->usb3v1 = NULL;
return -ENODEV;
}

static ssize_t twl4030_usb_vbus_show(struct device *dev,
Expand Down Expand Up @@ -598,7 +645,7 @@ static int __init twl4030_usb_probe(struct platform_device *pdev)
{
struct twl4030_usb_data *pdata = pdev->dev.platform_data;
struct twl4030_usb *twl;
int status;
int status, err;

if (!pdata) {
dev_dbg(&pdev->dev, "platform_data not available\n");
Expand All @@ -622,7 +669,12 @@ static int __init twl4030_usb_probe(struct platform_device *pdev)
/* init spinlock for workqueue */
spin_lock_init(&twl->lock);

twl4030_usb_ldo_init(twl);
err = twl4030_usb_ldo_init(twl);
if (err) {
dev_err(&pdev->dev, "ldo init failed\n");
kfree(twl);
return err;
}
otg_set_transceiver(&twl->otg);

platform_set_drvdata(pdev, twl);
Expand Down Expand Up @@ -688,6 +740,9 @@ static int __exit twl4030_usb_remove(struct platform_device *pdev)
twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB);

twl4030_phy_power(twl, 0);
regulator_put(twl->usb1v5);
regulator_put(twl->usb1v8);
regulator_put(twl->usb3v1);

kfree(twl);

Expand Down

0 comments on commit 6676016

Please sign in to comment.