summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRobert Jarzmik <robert.jarzmik@free.fr>2014-12-06 22:05:15 +0100
committerFelipe Balbi <balbi@ti.com>2015-01-12 12:13:29 -0600
commit7acc9973e3c42de9926b28eec8ae3434dfdde3be (patch)
tree48cc00abfbbb8608577bd186aec7b0b5094cc797
parent7bdea87a871f217a8a463f9203e6d0c702b7fda9 (diff)
usb: phy: generic: add vbus support
Add support for vbus detection and power supply. This code is more or less stolen from phy-gpio-vbus-usb.c, and aims at providing a detection mechanism for VBus (ie. usb cable plug) based on a GPIO line, and a power supply activation which draws current from the VBus. [ balbi@ti.com : fix build break ] Signed-off-by: Robert Jarzmik <robert.jarzmik@free.fr> Signed-off-by: Felipe Balbi <balbi@ti.com>
-rw-r--r--drivers/usb/phy/phy-generic.c91
-rw-r--r--drivers/usb/phy/phy-generic.h6
-rw-r--r--include/linux/usb/usb_phy_generic.h2
3 files changed, 98 insertions, 1 deletions
diff --git a/drivers/usb/phy/phy-generic.c b/drivers/usb/phy/phy-generic.c
index d53928f3a6e..dd05254241f 100644
--- a/drivers/usb/phy/phy-generic.c
+++ b/drivers/usb/phy/phy-generic.c
@@ -27,6 +27,7 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
+#include <linux/usb/gadget.h>
#include <linux/usb/otg.h>
#include <linux/usb/usb_phy_generic.h>
#include <linux/slab.h>
@@ -39,6 +40,9 @@
#include "phy-generic.h"
+#define VBUS_IRQ_FLAGS \
+ (IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
+
struct platform_device *usb_phy_generic_register(void)
{
return platform_device_register_simple("usb_phy_generic",
@@ -66,6 +70,73 @@ static void nop_reset_set(struct usb_phy_generic *nop, int asserted)
usleep_range(10000, 20000);
}
+/* interface to regulator framework */
+static void nop_set_vbus_draw(struct usb_phy_generic *nop, unsigned mA)
+{
+ struct regulator *vbus_draw = nop->vbus_draw;
+ int enabled;
+ int ret;
+
+ if (!vbus_draw)
+ return;
+
+ enabled = nop->vbus_draw_enabled;
+ if (mA) {
+ regulator_set_current_limit(vbus_draw, 0, 1000 * mA);
+ if (!enabled) {
+ ret = regulator_enable(vbus_draw);
+ if (ret < 0)
+ return;
+ nop->vbus_draw_enabled = 1;
+ }
+ } else {
+ if (enabled) {
+ ret = regulator_disable(vbus_draw);
+ if (ret < 0)
+ return;
+ nop->vbus_draw_enabled = 0;
+ }
+ }
+ nop->mA = mA;
+}
+
+
+static irqreturn_t nop_gpio_vbus_thread(int irq, void *data)
+{
+ struct usb_phy_generic *nop = data;
+ struct usb_otg *otg = nop->phy.otg;
+ int vbus, status;
+
+ vbus = gpiod_get_value(nop->gpiod_vbus);
+ if ((vbus ^ nop->vbus) == 0)
+ return IRQ_HANDLED;
+ nop->vbus = vbus;
+
+ if (vbus) {
+ status = USB_EVENT_VBUS;
+ otg->state = OTG_STATE_B_PERIPHERAL;
+ nop->phy.last_event = status;
+ usb_gadget_vbus_connect(otg->gadget);
+
+ /* drawing a "unit load" is *always* OK, except for OTG */
+ nop_set_vbus_draw(nop, 100);
+
+ atomic_notifier_call_chain(&nop->phy.notifier, status,
+ otg->gadget);
+ } else {
+ nop_set_vbus_draw(nop, 0);
+
+ usb_gadget_vbus_disconnect(otg->gadget);
+ status = USB_EVENT_NONE;
+ otg->state = OTG_STATE_B_IDLE;
+ nop->phy.last_event = status;
+
+ atomic_notifier_call_chain(&nop->phy.notifier, status,
+ otg->gadget);
+ }
+ return IRQ_HANDLED;
+}
+
int usb_gen_phy_init(struct usb_phy *phy)
{
struct usb_phy_generic *nop = dev_get_drvdata(phy->dev);
@@ -149,17 +220,23 @@ int usb_phy_gen_create_phy(struct device *dev, struct usb_phy_generic *nop,
needs_vcc = of_property_read_bool(node, "vcc-supply");
nop->gpiod_reset = devm_gpiod_get(dev, "reset-gpios");
err = PTR_ERR(nop->gpiod_reset);
+ if (!err) {
+ nop->gpiod_vbus = devm_gpiod_get(dev,
+ "vbus-detect-gpio");
+ err = PTR_ERR(nop->gpiod_vbus);
+ }
} else if (pdata) {
type = pdata->type;
clk_rate = pdata->clk_rate;
needs_vcc = pdata->needs_vcc;
- if (gpio_is_valid(gpio->gpio_reset)) {
+ if (gpio_is_valid(pdata->gpio_reset)) {
err = devm_gpio_request_one(dev, pdata->gpio_reset, 0,
dev_name(dev));
if (!err)
nop->gpiod_reset =
gpio_to_desc(pdata->gpio_reset);
}
+ nop->gpiod_vbus = pdata->gpiod_vbus;
}
if (err == -EPROBE_DEFER)
@@ -224,6 +301,18 @@ static int usb_phy_generic_probe(struct platform_device *pdev)
err = usb_phy_gen_create_phy(dev, nop, dev_get_platdata(&pdev->dev));
if (err)
return err;
+ if (nop->gpiod_vbus) {
+ err = devm_request_threaded_irq(&pdev->dev,
+ gpiod_to_irq(nop->gpiod_vbus),
+ NULL, nop_gpio_vbus_thread,
+ VBUS_IRQ_FLAGS, "vbus_detect",
+ nop);
+ if (err) {
+ dev_err(&pdev->dev, "can't request irq %i, err: %d\n",
+ gpiod_to_irq(nop->gpiod_vbus), err);
+ return err;
+ }
+ }
nop->phy.init = usb_gen_phy_init;
nop->phy.shutdown = usb_gen_phy_shutdown;
diff --git a/drivers/usb/phy/phy-generic.h b/drivers/usb/phy/phy-generic.h
index 09924fdaaab..0d0eadd54ed 100644
--- a/drivers/usb/phy/phy-generic.h
+++ b/drivers/usb/phy/phy-generic.h
@@ -3,6 +3,7 @@
#include <linux/usb/usb_phy_generic.h>
#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
struct usb_phy_generic {
struct usb_phy phy;
@@ -10,6 +11,11 @@ struct usb_phy_generic {
struct clk *clk;
struct regulator *vcc;
struct gpio_desc *gpiod_reset;
+ struct gpio_desc *gpiod_vbus;
+ struct regulator *vbus_draw;
+ bool vbus_draw_enabled;
+ unsigned long mA;
+ unsigned int vbus;
};
int usb_gen_phy_init(struct usb_phy *phy);
diff --git a/include/linux/usb/usb_phy_generic.h b/include/linux/usb/usb_phy_generic.h
index 68adae83aff..c13632d5292 100644
--- a/include/linux/usb/usb_phy_generic.h
+++ b/include/linux/usb/usb_phy_generic.h
@@ -2,6 +2,7 @@
#define __LINUX_USB_NOP_XCEIV_H
#include <linux/usb/otg.h>
+#include <linux/gpio/consumer.h>
struct usb_phy_generic_platform_data {
enum usb_phy_type type;
@@ -11,6 +12,7 @@ struct usb_phy_generic_platform_data {
unsigned int needs_vcc:1;
unsigned int needs_reset:1; /* deprecated */
int gpio_reset;
+ struct gpio_desc *gpiod_vbus;
};
#if IS_ENABLED(CONFIG_NOP_USB_XCEIV)