Skip to content

Commit

Permalink
usb: phy: generic: add vbus support
Browse files Browse the repository at this point in the history
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.

[ [email protected] : fix build break ]

Signed-off-by: Robert Jarzmik <[email protected]>
Signed-off-by: Felipe Balbi <[email protected]>
  • Loading branch information
rjarzmik authored and Felipe Balbi committed Jan 12, 2015
1 parent 7bdea87 commit 7acc997
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 1 deletion.
91 changes: 90 additions & 1 deletion drivers/usb/phy/phy-generic.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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",
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions drivers/usb/phy/phy-generic.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,19 @@

#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;
struct device *dev;
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);
Expand Down
2 changes: 2 additions & 0 deletions include/linux/usb/usb_phy_generic.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand Down

0 comments on commit 7acc997

Please sign in to comment.