[PATCH 2/3] at91-ohci: support overcurrent notification

Nicolas Ferre nicolas.ferre at atmel.com
Wed Sep 7 06:47:37 EDT 2011


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 at free-electrons.com>
> Cc: Andrew Victor <linux at maxim.org.za>
> Cc: Nicolas Ferre <nicolas.ferre at atmel.com>

Signed-off-by: Nicolas Ferre <nicolas.ferre at atmel.com>

> Cc: Jean-Christophe Plagniol-Villard <plagnioj at jcrosoft.com>
> Cc: Matthieu CASTET <matthieu.castet at 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




More information about the linux-arm-kernel mailing list