summaryrefslogtreecommitdiff
path: root/drivers/phy/ti/phy-twl4030-usb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/phy/ti/phy-twl4030-usb.c')
-rw-r--r--drivers/phy/ti/phy-twl4030-usb.c112
1 files changed, 66 insertions, 46 deletions
diff --git a/drivers/phy/ti/phy-twl4030-usb.c b/drivers/phy/ti/phy-twl4030-usb.c
index 2990b3965460..a26aec3ab29e 100644
--- a/drivers/phy/ti/phy-twl4030-usb.c
+++ b/drivers/phy/ti/phy-twl4030-usb.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* twl4030_usb - TWL4030 USB transceiver, talking to OMAP OTG controller
*
@@ -5,20 +6,6 @@
* Copyright (C) 2008 Nokia Corporation
* Contact: Felipe Balbi <felipe.balbi@nokia.com>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- *
* Current status:
* - HS USB ULPI mode works.
* - 3-pin mode support may be added in future.
@@ -27,6 +14,7 @@
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
+#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/io.h>
@@ -36,7 +24,7 @@
#include <linux/pm_runtime.h>
#include <linux/usb/musb.h>
#include <linux/usb/ulpi.h>
-#include <linux/i2c/twl.h>
+#include <linux/mfd/twl.h>
#include <linux/regulator/consumer.h>
#include <linux/err.h>
#include <linux/slab.h>
@@ -144,6 +132,7 @@
#define PMBR1 0x0D
#define GPIO_USB_4PIN_ULPI_2430C (3 << 0)
+static irqreturn_t twl4030_usb_irq(int irq, void *_twl);
/*
* If VBUS is valid or ID is ground, then we know a
* cable is present and we need to be runtime-enabled
@@ -171,8 +160,11 @@ struct twl4030_usb {
int irq;
enum musb_vbus_id_status linkstat;
+ atomic_t connected;
bool vbus_supplied;
bool musb_mailbox_pending;
+ unsigned long runtime_suspended:1;
+ unsigned long needs_resume:1;
struct delayed_work id_workaround_work;
};
@@ -185,7 +177,7 @@ struct twl4030_usb {
static int twl4030_i2c_write_u8_verify(struct twl4030_usb *twl,
u8 module, u8 data, u8 address)
{
- u8 check;
+ u8 check = 0xFF;
if ((twl_i2c_write_u8(module, data, address) >= 0) &&
(twl_i2c_read_u8(module, &check, address) >= 0) &&
@@ -395,6 +387,44 @@ static void __twl4030_phy_power(struct twl4030_usb *twl, int on)
WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0);
}
+static int twl4030_usb_runtime_suspend(struct device *dev);
+static int twl4030_usb_runtime_resume(struct device *dev);
+
+static int __maybe_unused twl4030_usb_suspend(struct device *dev)
+{
+ struct twl4030_usb *twl = dev_get_drvdata(dev);
+
+ /*
+ * we need enabled runtime on resume,
+ * so turn irq off here, so we do not get it early
+ * note: wakeup on usb plug works independently of this
+ */
+ dev_dbg(twl->dev, "%s\n", __func__);
+ disable_irq(twl->irq);
+ if (!twl->runtime_suspended && !atomic_read(&twl->connected)) {
+ twl4030_usb_runtime_suspend(dev);
+ twl->needs_resume = 1;
+ }
+
+ return 0;
+}
+
+static int __maybe_unused twl4030_usb_resume(struct device *dev)
+{
+ struct twl4030_usb *twl = dev_get_drvdata(dev);
+
+ dev_dbg(twl->dev, "%s\n", __func__);
+ enable_irq(twl->irq);
+ if (twl->needs_resume)
+ twl4030_usb_runtime_resume(dev);
+ /* check whether cable status changed */
+ twl4030_usb_irq(0, twl);
+
+ twl->runtime_suspended = 0;
+
+ return 0;
+}
+
static int __maybe_unused twl4030_usb_runtime_suspend(struct device *dev)
{
struct twl4030_usb *twl = dev_get_drvdata(dev);
@@ -406,6 +436,8 @@ static int __maybe_unused twl4030_usb_runtime_suspend(struct device *dev)
regulator_disable(twl->usb1v8);
regulator_disable(twl->usb3v1);
+ twl->runtime_suspended = 1;
+
return 0;
}
@@ -528,8 +560,8 @@ static int twl4030_usb_ldo_init(struct twl4030_usb *twl)
return 0;
}
-static ssize_t twl4030_usb_vbus_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+static ssize_t vbus_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
struct twl4030_usb *twl = dev_get_drvdata(dev);
int ret = -EINVAL;
@@ -541,45 +573,35 @@ static ssize_t twl4030_usb_vbus_show(struct device *dev,
return ret;
}
-static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL);
+static DEVICE_ATTR_RO(vbus);
static irqreturn_t twl4030_usb_irq(int irq, void *_twl)
{
struct twl4030_usb *twl = _twl;
enum musb_vbus_id_status status;
- bool status_changed = false;
int err;
status = twl4030_usb_linkstat(twl);
mutex_lock(&twl->lock);
- if (status >= 0 && status != twl->linkstat) {
- status_changed =
- cable_present(twl->linkstat) !=
- cable_present(status);
- twl->linkstat = status;
- }
+ twl->linkstat = status;
mutex_unlock(&twl->lock);
- if (status_changed) {
- /* FIXME add a set_power() method so that B-devices can
- * configure the charger appropriately. It's not always
- * correct to consume VBUS power, and how much current to
- * consume is a function of the USB configuration chosen
- * by the host.
- *
- * REVISIT usb_gadget_vbus_connect(...) as needed, ditto
- * its disconnect() sibling, when changing to/from the
- * USB_LINK_VBUS state. musb_hdrc won't care until it
- * starts to handle softconnect right.
- */
- if (cable_present(status)) {
+ if (cable_present(status)) {
+ if (atomic_add_unless(&twl->connected, 1, 1)) {
+ dev_dbg(twl->dev, "%s: cable connected %i\n",
+ __func__, status);
pm_runtime_get_sync(twl->dev);
- } else {
+ twl->musb_mailbox_pending = true;
+ }
+ } else {
+ if (atomic_add_unless(&twl->connected, -1, 0)) {
+ dev_dbg(twl->dev, "%s: cable disconnected %i\n",
+ __func__, status);
pm_runtime_mark_last_busy(twl->dev);
pm_runtime_put_autosuspend(twl->dev);
+ twl->musb_mailbox_pending = true;
}
- twl->musb_mailbox_pending = true;
}
if (twl->musb_mailbox_pending) {
err = musb_mailbox(status);
@@ -655,6 +677,7 @@ static const struct phy_ops ops = {
static const struct dev_pm_ops twl4030_usb_pm_ops = {
SET_RUNTIME_PM_OPS(twl4030_usb_runtime_suspend,
twl4030_usb_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(twl4030_usb_suspend, twl4030_usb_resume)
};
static int twl4030_usb_probe(struct platform_device *pdev)
@@ -761,18 +784,17 @@ static int twl4030_usb_probe(struct platform_device *pdev)
pm_runtime_mark_last_busy(&pdev->dev);
pm_runtime_put_autosuspend(twl->dev);
- dev_info(&pdev->dev, "Initialized TWL4030 USB module\n");
return 0;
}
-static int twl4030_usb_remove(struct platform_device *pdev)
+static void twl4030_usb_remove(struct platform_device *pdev)
{
struct twl4030_usb *twl = platform_get_drvdata(pdev);
int val;
usb_remove_phy(&twl->phy);
pm_runtime_get_sync(twl->dev);
- cancel_delayed_work(&twl->id_workaround_work);
+ cancel_delayed_work_sync(&twl->id_workaround_work);
device_remove_file(twl->dev, &dev_attr_vbus);
/* set transceiver mode to power on defaults */
@@ -799,8 +821,6 @@ static int twl4030_usb_remove(struct platform_device *pdev)
/* disable complete OTG block */
twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB);
-
- return 0;
}
#ifdef CONFIG_OF