Commit eb507025 authored by Robert Jarzmik's avatar Robert Jarzmik Committed by Greg Kroah-Hartman

USB: pxa27x_udc: factor pullup code to prepare otg transceiver

Prepare pxa27x_udc to handle usb D+ pullup properly : it
should connect the pullup resistor and disconnect it only
if no external transceiver is handling it.

[ dbrownell@users.sourceforge.net: kerneldoc and gpio fixes ]
Signed-off-by: default avatarRobert Jarzmik <robert.jarzmik@free.fr>
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent c2344f13
......@@ -30,6 +30,7 @@
#include <linux/proc_fs.h>
#include <linux/clk.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <asm/byteorder.h>
#include <mach/hardware.h>
......@@ -1471,6 +1472,32 @@ static struct usb_ep_ops pxa_ep_ops = {
.fifo_flush = pxa_ep_fifo_flush,
};
/**
* dplus_pullup - Connect or disconnect pullup resistor to D+ pin
* @udc: udc device
* @on: 0 if disconnect pullup resistor, 1 otherwise
* Context: any
*
* Handle D+ pullup resistor, make the device visible to the usb bus, and
* declare it as a full speed usb device
*/
static void dplus_pullup(struct pxa_udc *udc, int on)
{
if (on) {
if (gpio_is_valid(udc->mach->gpio_pullup))
gpio_set_value(udc->mach->gpio_pullup,
!udc->mach->gpio_pullup_inverted);
if (udc->mach->udc_command)
udc->mach->udc_command(PXA2XX_UDC_CMD_CONNECT);
} else {
if (gpio_is_valid(udc->mach->gpio_pullup))
gpio_set_value(udc->mach->gpio_pullup,
udc->mach->gpio_pullup_inverted);
if (udc->mach->udc_command)
udc->mach->udc_command(PXA2XX_UDC_CMD_DISCONNECT);
}
udc->pullup_on = on;
}
/**
* pxa_udc_get_frame - Returns usb frame number
......@@ -1500,21 +1527,91 @@ static int pxa_udc_wakeup(struct usb_gadget *_gadget)
return 0;
}
static void udc_enable(struct pxa_udc *udc);
static void udc_disable(struct pxa_udc *udc);
/**
* should_enable_udc - Tells if UDC should be enabled
* @udc: udc device
* Context: any
*
* The UDC should be enabled if :
* - the pullup resistor is connected
* - and a gadget driver is bound
*
* Returns 1 if UDC should be enabled, 0 otherwise
*/
static int should_enable_udc(struct pxa_udc *udc)
{
int put_on;
put_on = ((udc->pullup_on) && (udc->driver));
return put_on;
}
/**
* should_disable_udc - Tells if UDC should be disabled
* @udc: udc device
* Context: any
*
* The UDC should be disabled if :
* - the pullup resistor is not connected
* - or no gadget driver is bound
*
* Returns 1 if UDC should be disabled
*/
static int should_disable_udc(struct pxa_udc *udc)
{
int put_off;
put_off = ((!udc->pullup_on) || (!udc->driver));
return put_off;
}
/**
* pxa_udc_pullup - Offer manual D+ pullup control
* @_gadget: usb gadget using the control
* @is_active: 0 if disconnect, else connect D+ pullup resistor
* Context: !in_interrupt()
*
* Returns 0 if OK, -EOPNOTSUPP if udc driver doesn't handle D+ pullup
*/
static int pxa_udc_pullup(struct usb_gadget *_gadget, int is_active)
{
struct pxa_udc *udc = to_gadget_udc(_gadget);
if (!gpio_is_valid(udc->mach->gpio_pullup) && !udc->mach->udc_command)
return -EOPNOTSUPP;
dplus_pullup(udc, is_active);
if (should_enable_udc(udc))
udc_enable(udc);
if (should_disable_udc(udc))
udc_disable(udc);
return 0;
}
static const struct usb_gadget_ops pxa_udc_ops = {
.get_frame = pxa_udc_get_frame,
.wakeup = pxa_udc_wakeup,
.pullup = pxa_udc_pullup,
/* current versions must always be self-powered */
};
/**
* udc_disable - disable udc device controller
* @udc: udc device
* Context: any
*
* Disables the udc device : disables clocks, udc interrupts, control endpoint
* interrupts.
*/
static void udc_disable(struct pxa_udc *udc)
{
if (!udc->enabled)
return;
udc_writel(udc, UDCICR0, 0);
udc_writel(udc, UDCICR1, 0);
......@@ -1523,8 +1620,8 @@ static void udc_disable(struct pxa_udc *udc)
ep0_idle(udc);
udc->gadget.speed = USB_SPEED_UNKNOWN;
if (udc->mach->udc_command)
udc->mach->udc_command(PXA2XX_UDC_CMD_DISCONNECT);
udc->enabled = 0;
}
/**
......@@ -1570,6 +1667,9 @@ static __init void udc_init_data(struct pxa_udc *dev)
*/
static void udc_enable(struct pxa_udc *udc)
{
if (udc->enabled)
return;
udc_writel(udc, UDCICR0, 0);
udc_writel(udc, UDCICR1, 0);
udc_clear_mask_UDCCR(udc, UDCCR_UDE);
......@@ -1598,9 +1698,7 @@ static void udc_enable(struct pxa_udc *udc)
/* enable ep0 irqs */
pio_irq_enable(&udc->pxa_ep[0]);
dev_info(udc->dev, "UDC connecting\n");
if (udc->mach->udc_command)
udc->mach->udc_command(PXA2XX_UDC_CMD_CONNECT);
udc->enabled = 1;
}
/**
......@@ -1612,6 +1710,9 @@ static void udc_enable(struct pxa_udc *udc)
* usb traffic follows until a disconnect is reported. Then a host may connect
* again, or the driver might get unbound.
*
* Note that the udc is not automatically enabled. Check function
* should_enable_udc().
*
* Returns 0 if no error, -EINVAL, -ENODEV, -EBUSY otherwise
*/
int usb_gadget_register_driver(struct usb_gadget_driver *driver)
......@@ -1630,6 +1731,7 @@ int usb_gadget_register_driver(struct usb_gadget_driver *driver)
/* first hook up the driver ... */
udc->driver = driver;
udc->gadget.dev.driver = &driver->driver;
dplus_pullup(udc, 1);
retval = device_add(&udc->gadget.dev);
if (retval) {
......@@ -1645,6 +1747,7 @@ int usb_gadget_register_driver(struct usb_gadget_driver *driver)
dev_dbg(udc->dev, "registered gadget driver '%s'\n",
driver->driver.name);
if (should_enable_udc(udc))
udc_enable(udc);
return 0;
......@@ -1699,6 +1802,7 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver)
stop_activity(udc, driver);
udc_disable(udc);
dplus_pullup(udc, 0);
driver->unbind(&udc->gadget);
udc->driver = NULL;
......@@ -2212,7 +2316,7 @@ static int __init pxa_udc_probe(struct platform_device *pdev)
{
struct resource *regs;
struct pxa_udc *udc = &memory;
int retval;
int retval = 0, gpio;
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!regs)
......@@ -2224,6 +2328,19 @@ static int __init pxa_udc_probe(struct platform_device *pdev)
udc->dev = &pdev->dev;
udc->mach = pdev->dev.platform_data;
gpio = udc->mach->gpio_pullup;
if (gpio_is_valid(gpio)) {
retval = gpio_request(gpio, "USB D+ pullup");
if (retval == 0)
gpio_direction_output(gpio,
udc->mach->gpio_pullup_inverted);
}
if (retval) {
dev_err(&pdev->dev, "Couldn't request gpio %d : %d\n",
gpio, retval);
return retval;
}
udc->clk = clk_get(&pdev->dev, NULL);
if (IS_ERR(udc->clk)) {
retval = PTR_ERR(udc->clk);
......@@ -2273,10 +2390,13 @@ static int __init pxa_udc_probe(struct platform_device *pdev)
static int __exit pxa_udc_remove(struct platform_device *_dev)
{
struct pxa_udc *udc = platform_get_drvdata(_dev);
int gpio = udc->mach->gpio_pullup;
usb_gadget_unregister_driver(udc->driver);
free_irq(udc->irq, udc);
pxa_cleanup_debugfs(udc);
if (gpio_is_valid(gpio))
gpio_free(gpio);
platform_set_drvdata(_dev, NULL);
the_controller = NULL;
......@@ -2319,6 +2439,8 @@ static int pxa_udc_suspend(struct platform_device *_dev, pm_message_t state)
}
udc_disable(udc);
udc->pullup_resume = udc->pullup_on;
dplus_pullup(udc, 0);
return 0;
}
......@@ -2346,6 +2468,8 @@ static int pxa_udc_resume(struct platform_device *_dev)
ep->udccsr_value, ep->udccr_value);
}
dplus_pullup(udc, udc->pullup_resume);
if (should_enable_udc(udc))
udc_enable(udc);
/*
* We do not handle OTG yet.
......
......@@ -425,6 +425,9 @@ struct udc_stats {
* @stats: statistics on udc usage
* @udc_usb_ep: array of usb endpoints offered by the gadget
* @pxa_ep: array of pxa available endpoints
* @enabled: UDC was enabled by a previous udc_enable()
* @pullup_on: if pullup resistor connected to D+ pin
* @pullup_resume: if pullup resistor should be connected to D+ pin on resume
* @config: UDC active configuration
* @last_interface: UDC interface of the last SET_INTERFACE host request
* @last_alternate: UDC altsetting of the last SET_INTERFACE host request
......@@ -450,6 +453,9 @@ struct pxa_udc {
struct udc_usb_ep udc_usb_ep[NR_USB_ENDPOINTS];
struct pxa_ep pxa_ep[NR_PXA_ENDPOINTS];
unsigned enabled:1;
unsigned pullup_on:1;
unsigned pullup_resume:1;
unsigned config:2;
unsigned last_interface:3;
unsigned last_alternate:3;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment