All of lore.kernel.org
 help / color / mirror / Atom feed
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

  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.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.