From: nicolas.ferre@atmel•com (Nicolas Ferre)
To: linux-arm-kernel@lists•infradead.org
Subject: [PATCH 2/3] at91-ohci: support overcurrent notification
Date: Wed, 07 Sep 2011 12:47:37 +0200 [thread overview]
Message-ID: <4E674BC9.9060801@atmel.com> (raw)
In-Reply-To: <1310549358-13330-3-git-send-email-thomas.petazzoni@free-electrons.com>
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 <thomas.petazzoni@free-electrons•com>
> Cc: Andrew Victor <linux@maxim•org.za>
> Cc: Nicolas Ferre <nicolas.ferre@atmel•com>
Signed-off-by: Nicolas Ferre <nicolas.ferre@atmel•com>
> Cc: Jean-Christophe Plagniol-Villard <plagnioj@jcrosoft•com>
> Cc: Matthieu CASTET <matthieu.castet@parrot•com>
> ---
> 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
next prev parent reply other threads:[~2011-09-07 10:47 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-07-13 9:29 [PATCH v2] AT91 OHCI active-high vbus and overcurrent handling Thomas Petazzoni
2011-07-13 9:29 ` [PATCH 1/3] ohci-at91: add vbus_pin_inverted platform attribute Thomas Petazzoni
2011-09-07 10:47 ` Nicolas Ferre
2011-07-13 9:29 ` [PATCH 2/3] at91-ohci: support overcurrent notification Thomas Petazzoni
2011-07-13 14:28 ` Thomas Petazzoni
2011-07-13 15:54 ` Alan Stern
2011-07-13 16:43 ` Thomas Petazzoni
2011-07-13 17:17 ` Alan Stern
2011-09-07 10:47 ` Nicolas Ferre [this message]
2011-07-13 9:29 ` [PATCH 3/3] at91-ohci: configure overcurrent pins as input GPIOs Thomas Petazzoni
2011-09-07 10:51 ` Nicolas Ferre
2011-09-07 13:16 ` Nicolas Ferre
-- strict thread matches above, loose matches on Subject: below --
2011-07-05 9:05 [PATCH 1/3] ohci-at91: add vbus_pin_inverted platform attribute Thomas Petazzoni
2011-07-05 9:05 ` [PATCH 2/3] at91-ohci: support overcurrent notification Thomas Petazzoni
2011-07-05 10:12 ` Matthieu CASTET
2011-07-05 10:18 ` Thomas Petazzoni
2011-07-05 12:18 ` Matthieu CASTET
2011-07-05 14:23 ` Jean-Christophe PLAGNIOL-VILLARD
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4E674BC9.9060801@atmel.com \
--to=nicolas.ferre@atmel$(echo .)com \
--cc=linux-arm-kernel@lists$(echo .)infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox