linux/drivers/usb/chipidea/host.c

185 lines
4.1 KiB
C
Raw Normal View History

/*
* host.c - ChipIdea USB host controller driver
*
* Copyright (c) 2012 Intel Corporation
*
* Author: Alexander Shishkin
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>
#include <linux/usb/chipidea.h>
#include <linux/regulator/consumer.h>
#include "../host/ehci.h"
#include "ci.h"
#include "bits.h"
#include "host.h"
static struct hc_driver __read_mostly ci_ehci_hc_driver;
struct ehci_ci_priv {
struct regulator *reg_vbus;
};
static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable)
{
struct ehci_hcd *ehci = hcd_to_ehci(hcd);
struct ehci_ci_priv *priv = (struct ehci_ci_priv *)ehci->priv;
struct device *dev = hcd->self.controller;
struct ci_hdrc *ci = dev_get_drvdata(dev);
int ret = 0;
int port = HCS_N_PORTS(ehci->hcs_params);
if (priv->reg_vbus && !ci_otg_is_fsm_mode(ci)) {
if (port > 1) {
dev_warn(dev,
"Not support multi-port regulator control\n");
return 0;
}
if (enable)
ret = regulator_enable(priv->reg_vbus);
else
ret = regulator_disable(priv->reg_vbus);
if (ret) {
dev_err(dev,
"Failed to %s vbus regulator, ret=%d\n",
enable ? "enable" : "disable", ret);
return ret;
}
}
return 0;
};
static const struct ehci_driver_overrides ehci_ci_overrides = {
.extra_priv_size = sizeof(struct ehci_ci_priv),
.port_power = ehci_ci_portpower,
};
static irqreturn_t host_irq(struct ci_hdrc *ci)
{
return usb_hcd_irq(ci->irq, ci->hcd);
}
static int host_start(struct ci_hdrc *ci)
{
struct usb_hcd *hcd;
struct ehci_hcd *ehci;
struct ehci_ci_priv *priv;
int ret;
if (usb_disabled())
return -ENODEV;
hcd = usb_create_hcd(&ci_ehci_hc_driver, ci->dev, dev_name(ci->dev));
if (!hcd)
return -ENOMEM;
dev_set_drvdata(ci->dev, ci);
hcd->rsrc_start = ci->hw_bank.phys;
hcd->rsrc_len = ci->hw_bank.size;
hcd->regs = ci->hw_bank.abs;
hcd->has_tt = 1;
hcd->power_budget = ci->platdata->power_budget;
hcd->tpl_support = ci->platdata->tpl_support;
if (ci->phy)
hcd->phy = ci->phy;
else
hcd->usb_phy = ci->usb_phy;
ehci = hcd_to_ehci(hcd);
ehci->caps = ci->hw_bank.cap;
ehci->has_hostpc = ci->hw_bank.lpm;
ehci->has_tdi_phy_lpm = ci->hw_bank.lpm;
ehci->imx28_write_fix = ci->imx28_write_fix;
priv = (struct ehci_ci_priv *)ehci->priv;
priv->reg_vbus = NULL;
if (ci->platdata->reg_vbus)
priv->reg_vbus = ci->platdata->reg_vbus;
ret = usb_add_hcd(hcd, 0, 0);
if (ret) {
goto put_hcd;
} else {
struct usb_otg *otg = &ci->otg;
ci->hcd = hcd;
if (ci_otg_is_fsm_mode(ci)) {
otg->host = &hcd->self;
hcd->self.otg_port = 1;
}
}
if (ci->platdata->flags & CI_HDRC_DISABLE_STREAMING)
hw_write(ci, OP_USBMODE, USBMODE_CI_SDIS, USBMODE_CI_SDIS);
if (ci->platdata->flags & CI_HDRC_FORCE_FULLSPEED)
hw_write(ci, OP_PORTSC, PORTSC_PFSC, PORTSC_PFSC);
return ret;
put_hcd:
usb_put_hcd(hcd);
return ret;
}
static void host_stop(struct ci_hdrc *ci)
{
struct usb_hcd *hcd = ci->hcd;
usb/chipidea: fix oops on memory allocation failure When CMA fails to initialize in v3.12-rc4, the chipidea driver oopses the kernel while trying to remove and put the HCD which doesn't exist: WARNING: CPU: 0 PID: 6 at /home/rmk/git/linux-rmk/arch/arm/mm/dma-mapping.c:511 __dma_alloc+0x200/0x240() coherent pool not initialised! Modules linked in: CPU: 0 PID: 6 Comm: kworker/u2:0 Tainted: G W 3.12.0-rc4+ #56 Workqueue: deferwq deferred_probe_work_func Backtrace: [<c001218c>] (dump_backtrace+0x0/0x10c) from [<c0012328>] (show_stack+0x18/0x1c) r6:c05fd9cc r5:000001ff r4:00000000 r3:df86ad00 [<c0012310>] (show_stack+0x0/0x1c) from [<c05f3a4c>] (dump_stack+0x70/0x8c) [<c05f39dc>] (dump_stack+0x0/0x8c) from [<c00230a8>] (warn_slowpath_common+0x6c/0x8c) r4:df883a60 r3:df86ad00 [<c002303c>] (warn_slowpath_common+0x0/0x8c) from [<c002316c>] (warn_slowpath_fmt+0x38/0x40) r8:ffffffff r7:00001000 r6:c083b808 r5:00000000 r4:df2efe80 [<c0023134>] (warn_slowpath_fmt+0x0/0x40) from [<c00196bc>] (__dma_alloc+0x200/0x240) r3:00000000 r2:c05fda00 [<c00194bc>] (__dma_alloc+0x0/0x240) from [<c001982c>] (arm_dma_alloc+0x88/0xa0) [<c00197a4>] (arm_dma_alloc+0x0/0xa0) from [<c03e2904>] (ehci_setup+0x1f4/0x438) [<c03e2710>] (ehci_setup+0x0/0x438) from [<c03cbd60>] (usb_add_hcd+0x18c/0x664) [<c03cbbd4>] (usb_add_hcd+0x0/0x664) from [<c03e89f4>] (host_start+0xf0/0x180) [<c03e8904>] (host_start+0x0/0x180) from [<c03e7c34>] (ci_hdrc_probe+0x360/0x670 ) r6:df2ef410 r5:00000000 r4:df2c3010 r3:c03e8904 [<c03e78d4>] (ci_hdrc_probe+0x0/0x670) from [<c0311044>] (platform_drv_probe+0x20/0x24) [<c0311024>] (platform_drv_probe+0x0/0x24) from [<c030fcac>] (driver_probe_device+0x9c/0x234) ... ---[ end trace c88ccaf3969e8422 ]--- Unable to handle kernel NULL pointer dereference at virtual address 00000028 pgd = c0004000 [00000028] *pgd=00000000 Internal error: Oops: 17 [#1] SMP ARM Modules linked in: CPU: 0 PID: 6 Comm: kworker/u2:0 Tainted: G W 3.12.0-rc4+ #56 Workqueue: deferwq deferred_probe_work_func task: df86ad00 ti: df882000 task.ti: df882000 PC is at usb_remove_hcd+0x10/0x150 LR is at host_stop+0x1c/0x3c pc : [<c03cacec>] lr : [<c03e88e4>] psr: 60000013 sp : df883b50 ip : df883b78 fp : df883b74 r10: c11f4c54 r9 : c0836450 r8 : df30c400 r7 : fffffff4 r6 : df2ef410 r5 : 00000000 r4 : df2c3010 r3 : 00000000 r2 : 00000000 r1 : df86b0a0 r0 : 00000000 Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel Control: 10c53c7d Table: 2f29404a DAC: 00000015 Process kworker/u2:0 (pid: 6, stack limit = 0xdf882240) Stack: (0xdf883b50 to 0xdf884000) ... Backtrace: [<c03cacdc>] (usb_remove_hcd+0x0/0x150) from [<c03e88e4>] (host_stop+0x1c/0x3c) r6:df2ef410 r5:00000000 r4:df2c3010 [<c03e88c8>] (host_stop+0x0/0x3c) from [<c03e8aa0>] (ci_hdrc_host_destroy+0x1c/0x20) r5:00000000 r4:df2c3010 [<c03e8a84>] (ci_hdrc_host_destroy+0x0/0x20) from [<c03e7c80>] (ci_hdrc_probe+0x3ac/0x670) [<c03e78d4>] (ci_hdrc_probe+0x0/0x670) from [<c0311044>] (platform_drv_probe+0x20/0x24) [<c0311024>] (platform_drv_probe+0x0/0x24) from [<c030fcac>] (driver_probe_device+0x9c/0x234) [<c030fc10>] (driver_probe_device+0x0/0x234) from [<c030ff28>] (__device_attach+0x44/0x48) ... ---[ end trace c88ccaf3969e8423 ]--- Fix this so at least we can continue booting and get to a shell prompt. Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> Tested-by: Russell King <rmk+kernel@arm.linux.org.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2013-10-16 12:45:15 +00:00
if (hcd) {
usb_remove_hcd(hcd);
usb_put_hcd(hcd);
}
}
void ci_hdrc_host_destroy(struct ci_hdrc *ci)
{
if (ci->role == CI_ROLE_HOST && ci->hcd)
host_stop(ci);
}
int ci_hdrc_host_init(struct ci_hdrc *ci)
{
struct ci_role_driver *rdrv;
if (!hw_read(ci, CAP_DCCPARAMS, DCCPARAMS_HC))
return -ENXIO;
rdrv = devm_kzalloc(ci->dev, sizeof(struct ci_role_driver), GFP_KERNEL);
if (!rdrv)
return -ENOMEM;
rdrv->start = host_start;
rdrv->stop = host_stop;
rdrv->irq = host_irq;
rdrv->name = "host";
ci->roles[CI_ROLE_HOST] = rdrv;
ehci_init_driver(&ci_ehci_hc_driver, &ehci_ci_overrides);
return 0;
}