mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-07 11:28:32 +00:00
![Joachim Eastwood](/assets/img/avatar_default.png)
Fixes the following NULL pointer dereference: [ 7.740000] ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver [ 7.810000] Unable to handle kernel NULL pointer dereference at virtual address 00000028 [ 7.810000] pgd = c3a38000 [ 7.810000] [00000028] *pgd=23a8c831, *pte=00000000, *ppte=00000000 [ 7.810000] Internal error: Oops: 17 [#1] PREEMPT ARM [ 7.810000] Modules linked in: ohci_hcd(+) regmap_i2c snd_pcm usbcore snd_page_alloc at91_cf snd_timer pcmcia_rsrc snd soundcore gpio_keys regmap_spi pcmcia_core usb_common nls_base [ 7.810000] CPU: 0 Not tainted (3.6.0-rc6-mpa+ #264) [ 7.810000] PC is at __gpio_to_irq+0x18/0x40 [ 7.810000] LR is at ohci_hcd_at91_overcurrent_irq+0x24/0xb4 [ohci_hcd] [ 7.810000] pc : [<c01392d4>] lr : [<bf08f694>] psr: 40000093 [ 7.810000] sp : c3a11c40 ip : c3a11c50 fp : c3a11c4c [ 7.810000] r10: 00000000 r9 : c02dcd6e r8 : fefff400 [ 7.810000] r7 : 00000000 r6 : c02cc928 r5 : 00000030 r4 : c02dd168 [ 7.810000] r3 : c02e7350 r2 : ffffffea r1 : c02cc928 r0 : 00000000 [ 7.810000] Flags: nZcv IRQs off FIQs on Mode SVC_32 ISA ARM Segment user [ 7.810000] Control: c000717f Table: 23a38000 DAC: 00000015 [ 7.810000] Process modprobe (pid: 285, stack limit = 0xc3a10270) [ 7.810000] Stack: (0xc3a11c40 to 0xc3a12000) [ 7.810000] 1c40: c3a11c6c c3a11c50 bf08f694 c01392cc c3a11c84 c2c38b00 c3806900 00000030 [ 7.810000] 1c60: c3a11ca4 c3a11c70 c0051264 bf08f680 c3a11cac c3a11c80 c003e764 c3806900 [ 7.810000] 1c80: c2c38b00 c02cb05c c02cb000 fefff400 c3806930 c3a11cf4 c3a11cbc c3a11ca8 [ 7.810000] 1ca0: c005142c c005123c c3806900 c3805a00 c3a11cd4 c3a11cc0 c0053f24 c00513e4 [ 7.810000] 1cc0: c3a11cf4 00000030 c3a11cec c3a11cd8 c005120c c0053e88 00000000 00000000 [ 7.810000] 1ce0: c3a11d1c c3a11cf0 c00124d0 c00511e0 01400000 00000001 00000012 00000000 [ 7.810000] 1d00: ffffffff c3a11d94 00000030 00000000 c3a11d34 c3a11d20 c005120c c0012438 [ 7.810000] 1d20: c001dac4 00000012 c3a11d4c c3a11d38 c0009b08 c00511e0 c00523fc 60000013 [ 7.810000] 1d40: c3a11d5c c3a11d50 c0008510 c0009ab4 c3a11ddc c3a11d60 c0008eb4 c00084f0 [ 7.810000] 1d60: 00000000 00000030 00000000 00000080 60000013 bf08f670 c3806900 c2c38b00 [ 7.810000] 1d80: 00000030 c3806930 00000000 c3a11ddc c3a11d88 c3a11da8 c0054190 c00523fc [ 7.810000] 1da0: 60000013 ffffffff c3a11dec c3a11db8 00000000 c2c38b00 bf08f670 c3806900 [ 7.810000] 1dc0: 00000000 00000080 c02cc928 00000030 c3a11e0c c3a11de0 c0052764 c00520d8 [ 7.810000] 1de0: c3a11dfc 00000000 00000000 00000002 bf090f61 00000004 c02cc930 c02cc928 [ 7.810000] 1e00: c3a11e4c c3a11e10 bf090978 c005269c bf090f61 c02cc928 bf093000 c02dd170 [ 7.810000] 1e20: c3a11e3c c02cc930 c02cc930 bf0911d0 bf0911d0 bf093000 c3a10000 00000000 [ 7.810000] 1e40: c3a11e5c c3a11e50 c0155b7c bf090808 c3a11e7c c3a11e60 c0154690 c0155b6c [ 7.810000] 1e60: c02cc930 c02cc964 bf0911d0 c3a11ea0 c3a11e9c c3a11e80 c015484c c01545e8 [ 7.810000] 1e80: 00000000 00000000 c01547e4 bf0911d0 c3a11ec4 c3a11ea0 c0152e58 c01547f4 [ 7.810000] 1ea0: c381b88c c384ab10 c2c10540 bf0911d0 00000000 c02d7518 c3a11ed4 c3a11ec8 [ 7.810000] 1ec0: c01544c0 c0152e0c c3a11efc c3a11ed8 c01536cc c01544b0 bf091075 c3a11ee8 [ 7.810000] 1ee0: bf049af0 bf09120c bf0911d0 00000000 c3a11f1c c3a11f00 c0154e9c c0153628 [ 7.810000] 1f00: bf049af0 bf09120c 000ae190 00000000 c3a11f2c c3a11f20 c0155f58 c0154e04 [ 7.810000] 1f20: c3a11f44 c3a11f30 bf093054 c0155f1c 00000000 00006a4f c3a11f7c c3a11f48 [ 7.810000] 1f40: c0008638 bf093010 bf09120c 000ae190 00000000 c00093c4 00006a4f bf09120c [ 7.810000] 1f60: 000ae190 00000000 c00093c4 00000000 c3a11fa4 c3a11f80 c004fdc4 c000859c [ 7.810000] 1f80: c3a11fa4 000ae190 00006a4f 00016eb8 000ad018 00000080 00000000 c3a11fa8 [ 7.810000] 1fa0: c0009260 c004fd58 00006a4f 00016eb8 000ae190 00006a4f 000ae100 00000000 [ 7.810000] 1fc0: 00006a4f 00016eb8 000ad018 00000080 000adba0 000ad208 00000000 000ad3d8 [ 7.810000] 1fe0: beaf7ae8 beaf7ad8 000172b8 b6e4e940 20000010 000ae190 00000000 00000000 [ 7.810000] Backtrace: [ 7.810000] [<c01392bc>] (__gpio_to_irq+0x0/0x40) from [<bf08f694>] (ohci_hcd_at91_overcurrent_irq+0x24/0xb4 [ohci_hcd]) [ 7.810000] [<bf08f670>] (ohci_hcd_at91_overcurrent_irq+0x0/0xb4 [ohci_hcd]) from [<c0051264>] (handle_irq_event_percpu+0x38/0x1a8) [ 7.810000] r6:00000030 r5:c3806900 r4:c2c38b00 [ 7.810000] [<c005122c>] (handle_irq_event_percpu+0x0/0x1a8) from [<c005142c>] (handle_irq_event+0x58/0x7c) [ 7.810000] [<c00513d4>] (handle_irq_event+0x0/0x7c) from [<c0053f24>] (handle_simple_irq+0xac/0xd8) [ 7.810000] r5:c3805a00 r4:c3806900 [ 7.810000] [<c0053e78>] (handle_simple_irq+0x0/0xd8) from [<c005120c>] (generic_handle_irq+0x3c/0x48) [ 7.810000] r4:00000030 [ 7.810000] [<c00511d0>] (generic_handle_irq+0x0/0x48) from [<c00124d0>] (gpio_irq_handler+0xa8/0xfc) [ 7.810000] r4:00000000 [ 7.810000] [<c0012428>] (gpio_irq_handler+0x0/0xfc) from [<c005120c>] (generic_handle_irq+0x3c/0x48) [ 7.810000] [<c00511d0>] (generic_handle_irq+0x0/0x48) from [<c0009b08>] (handle_IRQ+0x64/0x88) [ 7.810000] r4:00000012 [ 7.810000] [<c0009aa4>] (handle_IRQ+0x0/0x88) from [<c0008510>] (at91_aic_handle_irq+0x30/0x38) [ 7.810000] r5:60000013 r4:c00523fc [ 7.810000] [<c00084e0>] (at91_aic_handle_irq+0x0/0x38) from [<c0008eb4>] (__irq_svc+0x34/0x60) [ 7.810000] Exception stack(0xc3a11d60 to 0xc3a11da8) [ 7.810000] 1d60: 00000000 00000030 00000000 00000080 60000013 bf08f670 c3806900 c2c38b00 [ 7.810000] 1d80: 00000030 c3806930 00000000 c3a11ddc c3a11d88 c3a11da8 c0054190 c00523fc [ 7.810000] 1da0: 60000013 ffffffff [ 7.810000] [<c00520c8>] (__setup_irq+0x0/0x458) from [<c0052764>] (request_threaded_irq+0xd8/0x134) [ 7.810000] [<c005268c>] (request_threaded_irq+0x0/0x134) from [<bf090978>] (ohci_hcd_at91_drv_probe+0x180/0x41c [ohci_hcd]) [ 7.810000] [<bf0907f8>] (ohci_hcd_at91_drv_probe+0x0/0x41c [ohci_hcd]) from [<c0155b7c>] (platform_drv_probe+0x20/0x24) [ 7.810000] [<c0155b5c>] (platform_drv_probe+0x0/0x24) from [<c0154690>] (driver_probe_device+0xb8/0x20c) [ 7.810000] [<c01545d8>] (driver_probe_device+0x0/0x20c) from [<c015484c>] (__driver_attach+0x68/0x88) [ 7.810000] r7:c3a11ea0 r6:bf0911d0 r5:c02cc964 r4:c02cc930 [ 7.810000] [<c01547e4>] (__driver_attach+0x0/0x88) from [<c0152e58>] (bus_for_each_dev+0x5c/0x9c) [ 7.810000] r6:bf0911d0 r5:c01547e4 r4:00000000 [ 7.810000] [<c0152dfc>] (bus_for_each_dev+0x0/0x9c) from [<c01544c0>] (driver_attach+0x20/0x28) [ 7.810000] r7:c02d7518 r6:00000000 r5:bf0911d0 r4:c2c10540 [ 7.810000] [<c01544a0>] (driver_attach+0x0/0x28) from [<c01536cc>] (bus_add_driver+0xb4/0x22c) [ 7.810000] [<c0153618>] (bus_add_driver+0x0/0x22c) from [<c0154e9c>] (driver_register+0xa8/0x144) [ 7.810000] r7:00000000 r6:bf0911d0 r5:bf09120c r4:bf049af0 [ 7.810000] [<c0154df4>] (driver_register+0x0/0x144) from [<c0155f58>] (platform_driver_register+0x4c/0x60) [ 7.810000] r7:00000000 r6:000ae190 r5:bf09120c r4:bf049af0 [ 7.810000] [<c0155f0c>] (platform_driver_register+0x0/0x60) from [<bf093054>] (ohci_hcd_mod_init+0x54/0x8c [ohci_hcd]) [ 7.810000] [<bf093000>] (ohci_hcd_mod_init+0x0/0x8c [ohci_hcd]) from [<c0008638>] (do_one_initcall+0xac/0x174) [ 7.810000] r4:00006a4f [ 7.810000] [<c000858c>] (do_one_initcall+0x0/0x174) from [<c004fdc4>] (sys_init_module+0x7c/0x1a0) [ 7.810000] [<c004fd48>] (sys_init_module+0x0/0x1a0) from [<c0009260>] (ret_fast_syscall+0x0/0x2c) [ 7.810000] r7:00000080 r6:000ad018 r5:00016eb8 r4:00006a4f [ 7.810000] Code: e24cb004 e59f3028 e1a02000 e7930180 (e5903028) [ 7.810000] ---[ end trace 85aa37ed128143b5 ]--- [ 7.810000] Kernel panic - not syncing: Fatal exception in interrupt Commit6fffb77c
(USB: ohci-at91: fix PIO handling in relation with number of ports) started setting unused pins to EINVAL. But this exposed a bug in the ohci_hcd_at91_overcurrent_irq function where the gpio was used without being checked to see if it is valid. This patches fixed the issue by adding the gpio valid check. Signed-off-by: Joachim Eastwood <joachim.eastwood@jotron.com> Cc: stable <stable@vger.kernel.org> # [3.4+] whereever6fffb77c
went Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
729 lines
16 KiB
C
729 lines
16 KiB
C
/*
|
|
* OHCI HCD (Host Controller Driver) for USB.
|
|
*
|
|
* Copyright (C) 2004 SAN People (Pty) Ltd.
|
|
* Copyright (C) 2005 Thibaut VARENE <varenet@parisc-linux.org>
|
|
*
|
|
* AT91 Bus Glue
|
|
*
|
|
* Based on fragments of 2.4 driver by Rick Bronson.
|
|
* Based on ohci-omap.c
|
|
*
|
|
* This file is licenced under the GPL.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/of_gpio.h>
|
|
|
|
#include <mach/hardware.h>
|
|
#include <asm/gpio.h>
|
|
|
|
#include <mach/board.h>
|
|
#include <mach/cpu.h>
|
|
|
|
#ifndef CONFIG_ARCH_AT91
|
|
#error "CONFIG_ARCH_AT91 must be defined."
|
|
#endif
|
|
|
|
#define valid_port(index) ((index) >= 0 && (index) < AT91_MAX_USBH_PORTS)
|
|
#define at91_for_each_port(index) \
|
|
for ((index) = 0; (index) < AT91_MAX_USBH_PORTS; (index)++)
|
|
|
|
/* interface and function clocks; sometimes also an AHB clock */
|
|
static struct clk *iclk, *fclk, *hclk;
|
|
static int clocked;
|
|
|
|
extern int usb_disabled(void);
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static void at91_start_clock(void)
|
|
{
|
|
clk_enable(hclk);
|
|
clk_enable(iclk);
|
|
clk_enable(fclk);
|
|
clocked = 1;
|
|
}
|
|
|
|
static void at91_stop_clock(void)
|
|
{
|
|
clk_disable(fclk);
|
|
clk_disable(iclk);
|
|
clk_disable(hclk);
|
|
clocked = 0;
|
|
}
|
|
|
|
static void at91_start_hc(struct platform_device *pdev)
|
|
{
|
|
struct usb_hcd *hcd = platform_get_drvdata(pdev);
|
|
struct ohci_regs __iomem *regs = hcd->regs;
|
|
|
|
dev_dbg(&pdev->dev, "start\n");
|
|
|
|
/*
|
|
* Start the USB clocks.
|
|
*/
|
|
at91_start_clock();
|
|
|
|
/*
|
|
* The USB host controller must remain in reset.
|
|
*/
|
|
writel(0, ®s->control);
|
|
}
|
|
|
|
static void at91_stop_hc(struct platform_device *pdev)
|
|
{
|
|
struct usb_hcd *hcd = platform_get_drvdata(pdev);
|
|
struct ohci_regs __iomem *regs = hcd->regs;
|
|
|
|
dev_dbg(&pdev->dev, "stop\n");
|
|
|
|
/*
|
|
* Put the USB host controller into reset.
|
|
*/
|
|
writel(0, ®s->control);
|
|
|
|
/*
|
|
* Stop the USB clocks.
|
|
*/
|
|
at91_stop_clock();
|
|
}
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static void __devexit usb_hcd_at91_remove (struct usb_hcd *, struct platform_device *);
|
|
|
|
/* configure so an HC device and id are always provided */
|
|
/* always called with process context; sleeping is OK */
|
|
|
|
|
|
/**
|
|
* usb_hcd_at91_probe - initialize AT91-based HCDs
|
|
* Context: !in_interrupt()
|
|
*
|
|
* Allocates basic resources for this USB host controller, and
|
|
* then invokes the start() method for the HCD associated with it
|
|
* through the hotplug entry's driver_data.
|
|
*/
|
|
static int __devinit usb_hcd_at91_probe(const struct hc_driver *driver,
|
|
struct platform_device *pdev)
|
|
{
|
|
int retval;
|
|
struct usb_hcd *hcd = NULL;
|
|
|
|
if (pdev->num_resources != 2) {
|
|
pr_debug("hcd probe: invalid num_resources");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if ((pdev->resource[0].flags != IORESOURCE_MEM)
|
|
|| (pdev->resource[1].flags != IORESOURCE_IRQ)) {
|
|
pr_debug("hcd probe: invalid resource type\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
hcd = usb_create_hcd(driver, &pdev->dev, "at91");
|
|
if (!hcd)
|
|
return -ENOMEM;
|
|
hcd->rsrc_start = pdev->resource[0].start;
|
|
hcd->rsrc_len = resource_size(&pdev->resource[0]);
|
|
|
|
if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) {
|
|
pr_debug("request_mem_region failed\n");
|
|
retval = -EBUSY;
|
|
goto err1;
|
|
}
|
|
|
|
hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len);
|
|
if (!hcd->regs) {
|
|
pr_debug("ioremap failed\n");
|
|
retval = -EIO;
|
|
goto err2;
|
|
}
|
|
|
|
iclk = clk_get(&pdev->dev, "ohci_clk");
|
|
if (IS_ERR(iclk)) {
|
|
dev_err(&pdev->dev, "failed to get ohci_clk\n");
|
|
retval = PTR_ERR(iclk);
|
|
goto err3;
|
|
}
|
|
fclk = clk_get(&pdev->dev, "uhpck");
|
|
if (IS_ERR(fclk)) {
|
|
dev_err(&pdev->dev, "failed to get uhpck\n");
|
|
retval = PTR_ERR(fclk);
|
|
goto err4;
|
|
}
|
|
hclk = clk_get(&pdev->dev, "hclk");
|
|
if (IS_ERR(hclk)) {
|
|
dev_err(&pdev->dev, "failed to get hclk\n");
|
|
retval = PTR_ERR(hclk);
|
|
goto err5;
|
|
}
|
|
|
|
at91_start_hc(pdev);
|
|
ohci_hcd_init(hcd_to_ohci(hcd));
|
|
|
|
retval = usb_add_hcd(hcd, pdev->resource[1].start, IRQF_SHARED);
|
|
if (retval == 0)
|
|
return retval;
|
|
|
|
/* Error handling */
|
|
at91_stop_hc(pdev);
|
|
|
|
clk_put(hclk);
|
|
err5:
|
|
clk_put(fclk);
|
|
err4:
|
|
clk_put(iclk);
|
|
|
|
err3:
|
|
iounmap(hcd->regs);
|
|
|
|
err2:
|
|
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
|
|
|
err1:
|
|
usb_put_hcd(hcd);
|
|
return retval;
|
|
}
|
|
|
|
|
|
/* may be called with controller, bus, and devices active */
|
|
|
|
/**
|
|
* usb_hcd_at91_remove - shutdown processing for AT91-based HCDs
|
|
* @dev: USB Host Controller being removed
|
|
* Context: !in_interrupt()
|
|
*
|
|
* Reverses the effect of usb_hcd_at91_probe(), first invoking
|
|
* the HCD's stop() method. It is always called from a thread
|
|
* context, "rmmod" or something similar.
|
|
*
|
|
*/
|
|
static void __devexit usb_hcd_at91_remove(struct usb_hcd *hcd,
|
|
struct platform_device *pdev)
|
|
{
|
|
usb_remove_hcd(hcd);
|
|
at91_stop_hc(pdev);
|
|
iounmap(hcd->regs);
|
|
release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
|
|
usb_put_hcd(hcd);
|
|
|
|
clk_put(hclk);
|
|
clk_put(fclk);
|
|
clk_put(iclk);
|
|
fclk = iclk = hclk = NULL;
|
|
|
|
dev_set_drvdata(&pdev->dev, NULL);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static int __devinit
|
|
ohci_at91_reset (struct usb_hcd *hcd)
|
|
{
|
|
struct at91_usbh_data *board = hcd->self.controller->platform_data;
|
|
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
|
int ret;
|
|
|
|
if ((ret = ohci_init(ohci)) < 0)
|
|
return ret;
|
|
|
|
ohci->num_ports = board->ports;
|
|
return 0;
|
|
}
|
|
|
|
static int __devinit
|
|
ohci_at91_start (struct usb_hcd *hcd)
|
|
{
|
|
struct ohci_hcd *ohci = hcd_to_ohci (hcd);
|
|
int ret;
|
|
|
|
if ((ret = ohci_run(ohci)) < 0) {
|
|
dev_err(hcd->self.controller, "can't start %s\n",
|
|
hcd->self.bus_name);
|
|
ohci_stop(hcd);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ohci_at91_usb_set_power(struct at91_usbh_data *pdata, int port, int enable)
|
|
{
|
|
if (!valid_port(port))
|
|
return;
|
|
|
|
if (!gpio_is_valid(pdata->vbus_pin[port]))
|
|
return;
|
|
|
|
gpio_set_value(pdata->vbus_pin[port],
|
|
pdata->vbus_pin_active_low[port] ^ enable);
|
|
}
|
|
|
|
static int ohci_at91_usb_get_power(struct at91_usbh_data *pdata, int port)
|
|
{
|
|
if (!valid_port(port))
|
|
return -EINVAL;
|
|
|
|
if (!gpio_is_valid(pdata->vbus_pin[port]))
|
|
return -EINVAL;
|
|
|
|
return gpio_get_value(pdata->vbus_pin[port]) ^
|
|
pdata->vbus_pin_active_low[port];
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
at91_for_each_port(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);
|
|
|
|
wIndex--;
|
|
|
|
switch (typeReq) {
|
|
case SetPortFeature:
|
|
if (wValue == USB_PORT_FEAT_POWER) {
|
|
dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n");
|
|
if (valid_port(wIndex)) {
|
|
ohci_at91_usb_set_power(pdata, wIndex, 1);
|
|
ret = 0;
|
|
}
|
|
|
|
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 (valid_port(wIndex)) {
|
|
pdata->overcurrent_changed[wIndex] = 0;
|
|
pdata->overcurrent_status[wIndex] = 0;
|
|
}
|
|
|
|
goto out;
|
|
|
|
case USB_PORT_FEAT_OVER_CURRENT:
|
|
dev_dbg(hcd->self.controller,
|
|
"ClearPortFeature: OVER_CURRENT\n");
|
|
|
|
if (valid_port(wIndex))
|
|
pdata->overcurrent_status[wIndex] = 0;
|
|
|
|
goto out;
|
|
|
|
case USB_PORT_FEAT_POWER:
|
|
dev_dbg(hcd->self.controller,
|
|
"ClearPortFeature: POWER\n");
|
|
|
|
if (valid_port(wIndex)) {
|
|
ohci_at91_usb_set_power(pdata, wIndex, 0);
|
|
return 0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
ret = ohci_hub_control(hcd, typeReq, wValue, wIndex + 1, 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 (valid_port(wIndex)) {
|
|
if (!ohci_at91_usb_get_power(pdata, wIndex))
|
|
*data &= ~cpu_to_le32(RH_PS_PPS);
|
|
|
|
if (pdata->overcurrent_changed[wIndex])
|
|
*data |= cpu_to_le32(RH_PS_OCIC);
|
|
|
|
if (pdata->overcurrent_status[wIndex])
|
|
*data |= cpu_to_le32(RH_PS_POCI);
|
|
}
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static const struct hc_driver ohci_at91_hc_driver = {
|
|
.description = hcd_name,
|
|
.product_desc = "AT91 OHCI",
|
|
.hcd_priv_size = sizeof(struct ohci_hcd),
|
|
|
|
/*
|
|
* generic hardware linkage
|
|
*/
|
|
.irq = ohci_irq,
|
|
.flags = HCD_USB11 | HCD_MEMORY,
|
|
|
|
/*
|
|
* basic lifecycle operations
|
|
*/
|
|
.reset = ohci_at91_reset,
|
|
.start = ohci_at91_start,
|
|
.stop = ohci_stop,
|
|
.shutdown = ohci_shutdown,
|
|
|
|
/*
|
|
* managing i/o requests and associated device resources
|
|
*/
|
|
.urb_enqueue = ohci_urb_enqueue,
|
|
.urb_dequeue = ohci_urb_dequeue,
|
|
.endpoint_disable = ohci_endpoint_disable,
|
|
|
|
/*
|
|
* scheduling support
|
|
*/
|
|
.get_frame_number = ohci_get_frame,
|
|
|
|
/*
|
|
* root hub support
|
|
*/
|
|
.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,
|
|
#endif
|
|
.start_port_reset = ohci_start_port_reset,
|
|
};
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
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 */
|
|
at91_for_each_port(port) {
|
|
if (gpio_is_valid(pdata->overcurrent_pin[port]) &&
|
|
gpio_to_irq(pdata->overcurrent_pin[port]) == irq) {
|
|
gpio = pdata->overcurrent_pin[port];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (port == AT91_MAX_USBH_PORTS) {
|
|
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;
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id at91_ohci_dt_ids[] = {
|
|
{ .compatible = "atmel,at91rm9200-ohci" },
|
|
{ /* sentinel */ }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(of, at91_ohci_dt_ids);
|
|
|
|
static u64 at91_ohci_dma_mask = DMA_BIT_MASK(32);
|
|
|
|
static int __devinit ohci_at91_of_init(struct platform_device *pdev)
|
|
{
|
|
struct device_node *np = pdev->dev.of_node;
|
|
int i, gpio;
|
|
enum of_gpio_flags flags;
|
|
struct at91_usbh_data *pdata;
|
|
u32 ports;
|
|
|
|
if (!np)
|
|
return 0;
|
|
|
|
/* Right now device-tree probed devices don't get dma_mask set.
|
|
* Since shared usb code relies on it, set it here for now.
|
|
* Once we have dma capability bindings this can go away.
|
|
*/
|
|
if (!pdev->dev.dma_mask)
|
|
pdev->dev.dma_mask = &at91_ohci_dma_mask;
|
|
|
|
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
|
if (!pdata)
|
|
return -ENOMEM;
|
|
|
|
if (!of_property_read_u32(np, "num-ports", &ports))
|
|
pdata->ports = ports;
|
|
|
|
at91_for_each_port(i) {
|
|
gpio = of_get_named_gpio_flags(np, "atmel,vbus-gpio", i, &flags);
|
|
pdata->vbus_pin[i] = gpio;
|
|
if (!gpio_is_valid(gpio))
|
|
continue;
|
|
pdata->vbus_pin_active_low[i] = flags & OF_GPIO_ACTIVE_LOW;
|
|
}
|
|
|
|
at91_for_each_port(i)
|
|
pdata->overcurrent_pin[i] =
|
|
of_get_named_gpio_flags(np, "atmel,oc-gpio", i, &flags);
|
|
|
|
pdev->dev.platform_data = pdata;
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static int __devinit ohci_at91_of_init(struct platform_device *pdev)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
static int __devinit ohci_hcd_at91_drv_probe(struct platform_device *pdev)
|
|
{
|
|
struct at91_usbh_data *pdata;
|
|
int i;
|
|
int gpio;
|
|
int ret;
|
|
|
|
ret = ohci_at91_of_init(pdev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
pdata = pdev->dev.platform_data;
|
|
|
|
if (pdata) {
|
|
at91_for_each_port(i) {
|
|
/*
|
|
* do not configure PIO if not in relation with
|
|
* real USB port on board
|
|
*/
|
|
if (i >= pdata->ports) {
|
|
pdata->vbus_pin[i] = -EINVAL;
|
|
pdata->overcurrent_pin[i] = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
if (!gpio_is_valid(pdata->vbus_pin[i]))
|
|
continue;
|
|
gpio = pdata->vbus_pin[i];
|
|
|
|
ret = gpio_request(gpio, "ohci_vbus");
|
|
if (ret) {
|
|
dev_err(&pdev->dev,
|
|
"can't request vbus gpio %d\n", gpio);
|
|
continue;
|
|
}
|
|
ret = gpio_direction_output(gpio,
|
|
!pdata->vbus_pin_active_low[i]);
|
|
if (ret) {
|
|
dev_err(&pdev->dev,
|
|
"can't put vbus gpio %d as output %d\n",
|
|
gpio, !pdata->vbus_pin_active_low[i]);
|
|
gpio_free(gpio);
|
|
continue;
|
|
}
|
|
|
|
ohci_at91_usb_set_power(pdata, i, 1);
|
|
}
|
|
|
|
at91_for_each_port(i) {
|
|
if (!gpio_is_valid(pdata->overcurrent_pin[i]))
|
|
continue;
|
|
gpio = pdata->overcurrent_pin[i];
|
|
|
|
ret = gpio_request(gpio, "ohci_overcurrent");
|
|
if (ret) {
|
|
dev_err(&pdev->dev,
|
|
"can't request overcurrent gpio %d\n",
|
|
gpio);
|
|
continue;
|
|
}
|
|
|
|
ret = gpio_direction_input(gpio);
|
|
if (ret) {
|
|
dev_err(&pdev->dev,
|
|
"can't configure overcurrent gpio %d as input\n",
|
|
gpio);
|
|
gpio_free(gpio);
|
|
continue;
|
|
}
|
|
|
|
ret = request_irq(gpio_to_irq(gpio),
|
|
ohci_hcd_at91_overcurrent_irq,
|
|
IRQF_SHARED, "ohci_overcurrent", pdev);
|
|
if (ret) {
|
|
gpio_free(gpio);
|
|
dev_err(&pdev->dev,
|
|
"can't get gpio IRQ for overcurrent\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
device_init_wakeup(&pdev->dev, 1);
|
|
return usb_hcd_at91_probe(&ohci_at91_hc_driver, pdev);
|
|
}
|
|
|
|
static int __devexit ohci_hcd_at91_drv_remove(struct platform_device *pdev)
|
|
{
|
|
struct at91_usbh_data *pdata = pdev->dev.platform_data;
|
|
int i;
|
|
|
|
if (pdata) {
|
|
at91_for_each_port(i) {
|
|
if (!gpio_is_valid(pdata->vbus_pin[i]))
|
|
continue;
|
|
ohci_at91_usb_set_power(pdata, i, 0);
|
|
gpio_free(pdata->vbus_pin[i]);
|
|
}
|
|
|
|
at91_for_each_port(i) {
|
|
if (!gpio_is_valid(pdata->overcurrent_pin[i]))
|
|
continue;
|
|
free_irq(gpio_to_irq(pdata->overcurrent_pin[i]), pdev);
|
|
gpio_free(pdata->overcurrent_pin[i]);
|
|
}
|
|
}
|
|
|
|
device_init_wakeup(&pdev->dev, 0);
|
|
usb_hcd_at91_remove(platform_get_drvdata(pdev), pdev);
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
static int
|
|
ohci_hcd_at91_drv_suspend(struct platform_device *pdev, pm_message_t mesg)
|
|
{
|
|
struct usb_hcd *hcd = platform_get_drvdata(pdev);
|
|
struct ohci_hcd *ohci = hcd_to_ohci(hcd);
|
|
|
|
if (device_may_wakeup(&pdev->dev))
|
|
enable_irq_wake(hcd->irq);
|
|
|
|
/*
|
|
* The integrated transceivers seem unable to notice disconnect,
|
|
* reconnect, or wakeup without the 48 MHz clock active. so for
|
|
* correctness, always discard connection state (using reset).
|
|
*
|
|
* REVISIT: some boards will be able to turn VBUS off...
|
|
*/
|
|
if (at91_suspend_entering_slow_clock()) {
|
|
ohci_usb_reset (ohci);
|
|
/* flush the writes */
|
|
(void) ohci_readl (ohci, &ohci->regs->control);
|
|
at91_stop_clock();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ohci_hcd_at91_drv_resume(struct platform_device *pdev)
|
|
{
|
|
struct usb_hcd *hcd = platform_get_drvdata(pdev);
|
|
|
|
if (device_may_wakeup(&pdev->dev))
|
|
disable_irq_wake(hcd->irq);
|
|
|
|
if (!clocked)
|
|
at91_start_clock();
|
|
|
|
ohci_finish_controller_resume(hcd);
|
|
return 0;
|
|
}
|
|
#else
|
|
#define ohci_hcd_at91_drv_suspend NULL
|
|
#define ohci_hcd_at91_drv_resume NULL
|
|
#endif
|
|
|
|
MODULE_ALIAS("platform:at91_ohci");
|
|
|
|
static struct platform_driver ohci_hcd_at91_driver = {
|
|
.probe = ohci_hcd_at91_drv_probe,
|
|
.remove = __devexit_p(ohci_hcd_at91_drv_remove),
|
|
.shutdown = usb_hcd_platform_shutdown,
|
|
.suspend = ohci_hcd_at91_drv_suspend,
|
|
.resume = ohci_hcd_at91_drv_resume,
|
|
.driver = {
|
|
.name = "at91_ohci",
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = of_match_ptr(at91_ohci_dt_ids),
|
|
},
|
|
};
|