From 0af4316babb64c6703c113a89462ff7843767f24 Mon Sep 17 00:00:00 2001 From: Jean-Christophe PLAGNIOL-VILLARD Date: Tue, 30 Aug 2011 03:29:28 +0200 Subject: at91: ohci-at91: always provide all the clocks Remove the cpu_is_at91xxxx() macros in the ohci-at91 driver. SoCs at91sam9261 and at91sam9g10 expect one additional clock: hck0. Signed-off-by: Jean-Christophe PLAGNIOL-VILLARD Signed-off-by: Nicolas Ferre --- drivers/usb/host/ohci-at91.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) (limited to 'drivers/usb') diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index 944291e10f97..e08cb10d1aca 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -35,8 +35,7 @@ extern int usb_disabled(void); static void at91_start_clock(void) { - if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) - clk_enable(hclk); + clk_enable(hclk); clk_enable(iclk); clk_enable(fclk); clocked = 1; @@ -46,8 +45,7 @@ static void at91_stop_clock(void) { clk_disable(fclk); clk_disable(iclk); - if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) - clk_disable(hclk); + clk_disable(hclk); clocked = 0; } @@ -142,8 +140,7 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver, iclk = clk_get(&pdev->dev, "ohci_clk"); fclk = clk_get(&pdev->dev, "uhpck"); - if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) - hclk = clk_get(&pdev->dev, "hck0"); + hclk = clk_get(&pdev->dev, "hclk"); at91_start_hc(pdev); ohci_hcd_init(hcd_to_ohci(hcd)); @@ -155,8 +152,7 @@ static int usb_hcd_at91_probe(const struct hc_driver *driver, /* Error handling */ at91_stop_hc(pdev); - if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) - clk_put(hclk); + clk_put(hclk); clk_put(fclk); clk_put(iclk); @@ -192,8 +188,7 @@ static void usb_hcd_at91_remove(struct usb_hcd *hcd, release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); - if (cpu_is_at91sam9261() || cpu_is_at91sam9g10()) - clk_put(hclk); + clk_put(hclk); clk_put(fclk); clk_put(iclk); fclk = iclk = hclk = NULL; -- cgit v1.2.3-59-g8ed1b From e7da859e424ccc30d2ef87dbabf655ad3d59f291 Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Wed, 13 Jul 2011 11:29:16 +0200 Subject: at91: ohci-at91: add vbus_pin_inverted platform attribute The existing OHCI AT91 driver made the assumption that the enable input of the USB power switch was active low. However, some USB power switches such as the Micrel MIC2026-1 [1] have an active high input to enable the power. A new vbus_pin_inverted attribute is added to the at91_usbh_data structure so that board files can tell the OHCI driver if the vbus pin logic is active low or active high. [1] http://www.micrel.com/page.do?page=product-info/products/mic2026.shtml Signed-off-by: Thomas Petazzoni Signed-off-by: Nicolas Ferre --- arch/arm/mach-at91/include/mach/board.h | 1 + drivers/usb/host/ohci-at91.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/usb') diff --git a/arch/arm/mach-at91/include/mach/board.h b/arch/arm/mach-at91/include/mach/board.h index ed544a0d5a1d..61d52dc05051 100644 --- a/arch/arm/mach-at91/include/mach/board.h +++ b/arch/arm/mach-at91/include/mach/board.h @@ -98,6 +98,7 @@ extern void __init at91_add_device_eth(struct at91_eth_data *data); struct at91_usbh_data { u8 ports; /* number of ports on root hub */ u8 vbus_pin[2]; /* port power-control pin */ + u8 vbus_pin_inverted; }; 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 e08cb10d1aca..5dd381fc2ddb 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -279,7 +279,7 @@ static int ohci_hcd_at91_drv_probe(struct platform_device *pdev) if (pdata->vbus_pin[i] <= 0) continue; gpio_request(pdata->vbus_pin[i], "ohci_vbus"); - gpio_direction_output(pdata->vbus_pin[i], 0); + gpio_direction_output(pdata->vbus_pin[i], pdata->vbus_pin_inverted); } } @@ -296,7 +296,7 @@ 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], 1); + gpio_direction_output(pdata->vbus_pin[i], !pdata->vbus_pin_inverted); gpio_free(pdata->vbus_pin[i]); } } -- cgit v1.2.3-59-g8ed1b From aa6e52a35d388e730f4df0ec2ec48294590cc459 Mon Sep 17 00:00:00 2001 From: Thomas Petazzoni Date: Wed, 13 Jul 2011 11:29:17 +0200 Subject: at91: at91-ohci: support overcurrent notification 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 Signed-off-by: Nicolas Ferre --- arch/arm/mach-at91/include/mach/board.h | 4 + drivers/usb/host/ohci-at91.c | 224 ++++++++++++++++++++++++++++++-- 2 files changed, 219 insertions(+), 9 deletions(-) (limited to 'drivers/usb') diff --git a/arch/arm/mach-at91/include/mach/board.h b/arch/arm/mach-at91/include/mach/board.h index 61d52dc05051..d07767f4052e 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 5dd381fc2ddb..ba3a46b78b75 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -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 = { @@ -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, @@ -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"); + } } } @@ -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); -- cgit v1.2.3-59-g8ed1b