Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 272820
b: refs/heads/master
c: aa6e52a
h: refs/heads/master
v: v3
  • Loading branch information
Thomas Petazzoni authored and Arnd Bergmann committed Sep 10, 2011
1 parent f8764c3 commit abd3682
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 10 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: e7da859e424ccc30d2ef87dbabf655ad3d59f291
refs/heads/master: aa6e52a35d388e730f4df0ec2ec48294590cc459
4 changes: 4 additions & 0 deletions trunk/arch/arm/mach-at91/include/mach/board.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ struct at91_usbh_data {
u8 ports; /* number of ports on root hub */
u8 vbus_pin[2]; /* port power-control pin */
u8 vbus_pin_inverted;
u8 overcurrent_supported;
u8 overcurrent_pin[2];
u8 overcurrent_status[2];
u8 overcurrent_changed[2];
};
extern void __init at91_add_device_usbh(struct at91_usbh_data *data);
extern void __init at91_add_device_usbh_ohci(struct at91_usbh_data *data);
Expand Down
224 changes: 215 additions & 9 deletions trunk/drivers/usb/host/ohci-at91.c
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,156 @@ ohci_at91_start (struct usb_hcd *hcd)
return 0;
}

static void ohci_at91_usb_set_power(struct at91_usbh_data *pdata, int port, int enable)
{
if (port < 0 || port >= 2)
return;

gpio_set_value(pdata->vbus_pin[port], !pdata->vbus_pin_inverted ^ enable);
}

static int ohci_at91_usb_get_power(struct at91_usbh_data *pdata, int port)
{
if (port < 0 || port >= 2)
return -EINVAL;

return gpio_get_value(pdata->vbus_pin[port]) ^ !pdata->vbus_pin_inverted;
}

/*
* Update the status data from the hub with the over-current indicator change.
*/
static int ohci_at91_hub_status_data(struct usb_hcd *hcd, char *buf)
{
struct at91_usbh_data *pdata = hcd->self.controller->platform_data;
int length = ohci_hub_status_data(hcd, buf);
int port;

for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) {
if (pdata->overcurrent_changed[port]) {
if (! length)
length = 1;
buf[0] |= 1 << (port + 1);
}
}

return length;
}

/*
* Look at the control requests to the root hub and see if we need to override.
*/
static int ohci_at91_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
u16 wIndex, char *buf, u16 wLength)
{
struct at91_usbh_data *pdata = hcd->self.controller->platform_data;
struct usb_hub_descriptor *desc;
int ret = -EINVAL;
u32 *data = (u32 *)buf;

dev_dbg(hcd->self.controller,
"ohci_at91_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x)\n",
hcd, typeReq, wValue, wIndex, buf, wLength);

switch (typeReq) {
case SetPortFeature:
if (wValue == USB_PORT_FEAT_POWER) {
dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n");
ohci_at91_usb_set_power(pdata, wIndex - 1, 1);
goto out;
}
break;

case ClearPortFeature:
switch (wValue) {
case USB_PORT_FEAT_C_OVER_CURRENT:
dev_dbg(hcd->self.controller,
"ClearPortFeature: C_OVER_CURRENT\n");

if (wIndex == 1 || wIndex == 2) {
pdata->overcurrent_changed[wIndex-1] = 0;
pdata->overcurrent_status[wIndex-1] = 0;
}

goto out;

case USB_PORT_FEAT_OVER_CURRENT:
dev_dbg(hcd->self.controller,
"ClearPortFeature: OVER_CURRENT\n");

if (wIndex == 1 || wIndex == 2) {
pdata->overcurrent_status[wIndex-1] = 0;
}

goto out;

case USB_PORT_FEAT_POWER:
dev_dbg(hcd->self.controller,
"ClearPortFeature: POWER\n");

if (wIndex == 1 || wIndex == 2) {
ohci_at91_usb_set_power(pdata, wIndex - 1, 0);
return 0;
}
}
break;
}

ret = ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
if (ret)
goto out;

switch (typeReq) {
case GetHubDescriptor:

/* update the hub's descriptor */

desc = (struct usb_hub_descriptor *)buf;

dev_dbg(hcd->self.controller, "wHubCharacteristics 0x%04x\n",
desc->wHubCharacteristics);

/* remove the old configurations for power-switching, and
* over-current protection, and insert our new configuration
*/

desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM);
desc->wHubCharacteristics |= cpu_to_le16(0x0001);

if (pdata->overcurrent_supported) {
desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_OCPM);
desc->wHubCharacteristics |= cpu_to_le16(0x0008|0x0001);
}

dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n",
desc->wHubCharacteristics);

return ret;

case GetPortStatus:
/* check port status */

dev_dbg(hcd->self.controller, "GetPortStatus(%d)\n", wIndex);

if (wIndex == 1 || wIndex == 2) {
if (! ohci_at91_usb_get_power(pdata, wIndex-1)) {
*data &= ~cpu_to_le32(RH_PS_PPS);
}

if (pdata->overcurrent_changed[wIndex-1]) {
*data |= cpu_to_le32(RH_PS_OCIC);
}

if (pdata->overcurrent_status[wIndex-1]) {
*data |= cpu_to_le32(RH_PS_POCI);
}
}
}

out:
return ret;
}

/*-------------------------------------------------------------------------*/

static const struct hc_driver ohci_at91_hc_driver = {
Expand Down Expand Up @@ -253,8 +403,8 @@ static const struct hc_driver ohci_at91_hc_driver = {
/*
* root hub support
*/
.hub_status_data = ohci_hub_status_data,
.hub_control = ohci_hub_control,
.hub_status_data = ohci_at91_hub_status_data,
.hub_control = ohci_at91_hub_control,
#ifdef CONFIG_PM
.bus_suspend = ohci_bus_suspend,
.bus_resume = ohci_bus_resume,
Expand All @@ -264,22 +414,71 @@ static const struct hc_driver ohci_at91_hc_driver = {

/*-------------------------------------------------------------------------*/

static irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data)
{
struct platform_device *pdev = data;
struct at91_usbh_data *pdata = pdev->dev.platform_data;
int val, gpio, port;

/* From the GPIO notifying the over-current situation, find
* out the corresponding port */
gpio = irq_to_gpio(irq);
for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) {
if (pdata->overcurrent_pin[port] == gpio)
break;
}

if (port == ARRAY_SIZE(pdata->overcurrent_pin)) {
dev_err(& pdev->dev, "overcurrent interrupt from unknown GPIO\n");
return IRQ_HANDLED;
}

val = gpio_get_value(gpio);

/* When notified of an over-current situation, disable power
on the corresponding port, and mark this port in
over-current. */
if (! val) {
ohci_at91_usb_set_power(pdata, port, 0);
pdata->overcurrent_status[port] = 1;
pdata->overcurrent_changed[port] = 1;
}

dev_dbg(& pdev->dev, "overcurrent situation %s\n",
val ? "exited" : "notified");

return IRQ_HANDLED;
}

/*-------------------------------------------------------------------------*/

static int ohci_hcd_at91_drv_probe(struct platform_device *pdev)
{
struct at91_usbh_data *pdata = pdev->dev.platform_data;
int i;

if (pdata) {
/* REVISIT make the driver support per-port power switching,
* and also overcurrent detection. Here we assume the ports
* are always powered while this driver is active, and use
* active-low power switches.
*/
for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) {
if (pdata->vbus_pin[i] <= 0)
continue;
gpio_request(pdata->vbus_pin[i], "ohci_vbus");
gpio_direction_output(pdata->vbus_pin[i], pdata->vbus_pin_inverted);
ohci_at91_usb_set_power(pdata, i, 1);
}

for (i = 0; i < ARRAY_SIZE(pdata->overcurrent_pin); i++) {
int ret;

if (pdata->overcurrent_pin[i] <= 0)
continue;
gpio_request(pdata->overcurrent_pin[i], "ohci_overcurrent");

ret = request_irq(gpio_to_irq(pdata->overcurrent_pin[i]),
ohci_hcd_at91_overcurrent_irq,
IRQF_SHARED, "ohci_overcurrent", pdev);
if (ret) {
gpio_free(pdata->overcurrent_pin[i]);
dev_warn(& pdev->dev, "cannot get GPIO IRQ for overcurrent\n");
}
}
}

Expand All @@ -296,9 +495,16 @@ static int ohci_hcd_at91_drv_remove(struct platform_device *pdev)
for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) {
if (pdata->vbus_pin[i] <= 0)
continue;
gpio_direction_output(pdata->vbus_pin[i], !pdata->vbus_pin_inverted);
ohci_at91_usb_set_power(pdata, i, 0);
gpio_free(pdata->vbus_pin[i]);
}

for (i = 0; i < ARRAY_SIZE(pdata->overcurrent_pin); i++) {
if (pdata->overcurrent_pin[i] <= 0)
continue;
free_irq(gpio_to_irq(pdata->overcurrent_pin[i]), pdev);
gpio_free(pdata->overcurrent_pin[i]);
}
}

device_init_wakeup(&pdev->dev, 0);
Expand Down

0 comments on commit abd3682

Please sign in to comment.