From mboxrd@z Thu Jan 1 00:00:00 1970 From: nicolas.ferre@atmel.com (Nicolas Ferre) Date: Wed, 07 Sep 2011 12:47:37 +0200 Subject: [PATCH 2/3] at91-ohci: support overcurrent notification In-Reply-To: <1310549358-13330-3-git-send-email-thomas.petazzoni@free-electrons.com> References: <1310549358-13330-1-git-send-email-thomas.petazzoni@free-electrons.com> <1310549358-13330-3-git-send-email-thomas.petazzoni@free-electrons.com> Message-ID: <4E674BC9.9060801@atmel.com> To: linux-arm-kernel@lists.infradead.org List-Id: linux-arm-kernel.lists.infradead.org Le 13/07/2011 11:29, Thomas Petazzoni : > Several USB power switches (AIC1526 or MIC2026) have a digital output > that is used to notify that an overcurrent situation is taking > place. This digital outputs are typically connected to GPIO inputs of > the processor and can be used to be notified of those overcurrent > situations. > > Therefore, we add a new overcurrent_pin[] array in the at91_usbh_data > structure so that boards can tell the AT91 OHCI driver which pins are > used for the overcurrent notification, and an overcurrent_supported > boolean to tell the driver whether overcurrent is supported or not. > > The code has been largely borrowed from ohci-da8xx.c and > ohci-s3c2410.c. > > Signed-off-by: Thomas Petazzoni > Cc: Andrew Victor > Cc: Nicolas Ferre Signed-off-by: Nicolas Ferre > Cc: Jean-Christophe Plagniol-Villard > Cc: Matthieu CASTET > --- > arch/arm/mach-at91/include/mach/board.h | 4 + > drivers/usb/host/ohci-at91.c | 224 +++++++++++++++++++++++++++++-- > 2 files changed, 219 insertions(+), 9 deletions(-) > > diff --git a/arch/arm/mach-at91/include/mach/board.h b/arch/arm/mach-at91/include/mach/board.h > index 61d52dc..d07767f 100644 > --- a/arch/arm/mach-at91/include/mach/board.h > +++ b/arch/arm/mach-at91/include/mach/board.h > @@ -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); > diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c > index 3612ccd..331909f 100644 > --- a/drivers/usb/host/ohci-at91.c > +++ b/drivers/usb/host/ohci-at91.c > @@ -223,6 +223,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 = { > @@ -258,8 +408,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, > @@ -269,22 +419,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"); > + } > } > } > > @@ -301,9 +500,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); -- Nicolas Ferre