/* ehci-msm.c - HSUSB Host Controller Driver Implementation * * Copyright (c) 2008-2012, The Linux Foundation. All rights reserved. * * Partly derived from ehci-fsl.c and ehci-hcd.c * Copyright (c) 2000-2004 by David Brownell * Copyright (c) 2005 MontaVista Software * * All source code in this file is licensed under the following license except * where indicated. * * 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, you can find it at http://www.fsf.org */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MSM_USB_BASE (hcd->regs) struct msmusb_hcd { struct ehci_hcd ehci; struct clk *alt_core_clk; struct clk *iface_clk; unsigned in_lpm; struct work_struct lpm_exit_work; spinlock_t lock; struct wake_lock wlock; unsigned int clk_enabled; struct msm_usb_host_platform_data *pdata; unsigned running; struct usb_phy *xceiv; struct work_struct otg_work; unsigned flags; struct msm_otg_ops otg_ops; }; static inline struct msmusb_hcd *hcd_to_mhcd(struct usb_hcd *hcd) { return (struct msmusb_hcd *) (hcd->hcd_priv); } static inline struct usb_hcd *mhcd_to_hcd(struct msmusb_hcd *mhcd) { return container_of((void *) mhcd, struct usb_hcd, hcd_priv); } static void msm_xusb_pm_qos_update(struct msmusb_hcd *mhcd, int vote) { struct msm_usb_host_platform_data *pdata = mhcd->pdata; /* if otg driver is available, it would take * care of voting for appropriate pclk source */ if (mhcd->xceiv) return; if (vote) clk_prepare_enable(pdata->ebi1_clk); else clk_disable_unprepare(pdata->ebi1_clk); } static void msm_xusb_enable_clks(struct msmusb_hcd *mhcd) { struct msm_usb_host_platform_data *pdata = mhcd->pdata; if (mhcd->clk_enabled) return; switch (PHY_TYPE(pdata->phy_info)) { case USB_PHY_INTEGRATED: /* OTG driver takes care of clock management */ break; case USB_PHY_SERIAL_PMIC: clk_prepare_enable(mhcd->alt_core_clk); clk_prepare_enable(mhcd->iface_clk); break; default: pr_err("%s: undefined phy type ( %X )\n", __func__, pdata->phy_info); return; } mhcd->clk_enabled = 1; } static void msm_xusb_disable_clks(struct msmusb_hcd *mhcd) { struct msm_usb_host_platform_data *pdata = mhcd->pdata; if (!mhcd->clk_enabled) return; switch (PHY_TYPE(pdata->phy_info)) { case USB_PHY_INTEGRATED: /* OTG driver takes care of clock management */ break; case USB_PHY_SERIAL_PMIC: clk_disable_unprepare(mhcd->alt_core_clk); clk_disable_unprepare(mhcd->iface_clk); break; default: pr_err("%s: undefined phy type ( %X )\n", __func__, pdata->phy_info); return; } mhcd->clk_enabled = 0; } static int usb_wakeup_phy(struct usb_hcd *hcd) { struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); struct msm_usb_host_platform_data *pdata = mhcd->pdata; int ret = -ENODEV; switch (PHY_TYPE(pdata->phy_info)) { case USB_PHY_INTEGRATED: break; case USB_PHY_SERIAL_PMIC: ret = msm_fsusb_resume_phy(); break; default: pr_err("%s: undefined phy type ( %X ) \n", __func__, pdata->phy_info); } return ret; } #ifdef CONFIG_PM static int usb_suspend_phy(struct usb_hcd *hcd) { int ret = 0; struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); struct msm_usb_host_platform_data *pdata = mhcd->pdata; switch (PHY_TYPE(pdata->phy_info)) { case USB_PHY_INTEGRATED: break; case USB_PHY_SERIAL_PMIC: ret = msm_fsusb_set_remote_wakeup(); ret = msm_fsusb_suspend_phy(); break; default: pr_err("%s: undefined phy type ( %X ) \n", __func__, pdata->phy_info); ret = -ENODEV; break; } return ret; } static int usb_lpm_enter(struct usb_hcd *hcd) { struct device *dev = container_of((void *)hcd, struct device, platform_data); struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); disable_irq(hcd->irq); if (mhcd->in_lpm) { pr_info("%s: already in lpm. nothing to do\n", __func__); enable_irq(hcd->irq); return 0; } if (HC_IS_RUNNING(hcd->state)) { pr_info("%s: can't enter into lpm. controller is runnning\n", __func__); enable_irq(hcd->irq); return -1; } pr_info("%s: lpm enter procedure started\n", __func__); mhcd->in_lpm = 1; if (usb_suspend_phy(hcd)) { mhcd->in_lpm = 0; enable_irq(hcd->irq); pr_info("phy suspend failed\n"); pr_info("%s: lpm enter procedure end\n", __func__); return -1; } msm_xusb_disable_clks(mhcd); if (mhcd->xceiv && mhcd->xceiv->set_suspend) mhcd->xceiv->set_suspend(mhcd->xceiv, 1); if (device_may_wakeup(dev)) enable_irq_wake(hcd->irq); enable_irq(hcd->irq); pr_info("%s: lpm enter procedure end\n", __func__); return 0; } #endif void usb_lpm_exit_w(struct work_struct *work) { struct msmusb_hcd *mhcd = container_of((void *) work, struct msmusb_hcd, lpm_exit_work); struct usb_hcd *hcd = mhcd_to_hcd(mhcd); struct device *dev = container_of((void *)hcd, struct device, platform_data); msm_xusb_enable_clks(mhcd); if (usb_wakeup_phy(hcd)) { pr_err("fatal error: cannot bring phy out of lpm\n"); return; } /* If resume signalling finishes before lpm exit, PCD is not set in * USBSTS register. Drive resume signal to the downstream device now * so that EHCI can process the upcoming port change interrupt.*/ writel(readl(USB_PORTSC) | PORTSC_FPR, USB_PORTSC); if (mhcd->xceiv && mhcd->xceiv->set_suspend) mhcd->xceiv->set_suspend(mhcd->xceiv, 0); if (device_may_wakeup(dev)) disable_irq_wake(hcd->irq); enable_irq(hcd->irq); } static void usb_lpm_exit(struct usb_hcd *hcd) { unsigned long flags; struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); spin_lock_irqsave(&mhcd->lock, flags); if (!mhcd->in_lpm) { spin_unlock_irqrestore(&mhcd->lock, flags); return; } mhcd->in_lpm = 0; disable_irq_nosync(hcd->irq); schedule_work(&mhcd->lpm_exit_work); spin_unlock_irqrestore(&mhcd->lock, flags); } static irqreturn_t ehci_msm_irq(struct usb_hcd *hcd) { struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); struct msm_otg *otg = container_of(mhcd->xceiv, struct msm_otg, phy); /* * OTG scheduled a work to get Integrated PHY out of LPM, * WAIT till then */ if (PHY_TYPE(mhcd->pdata->phy_info) == USB_PHY_INTEGRATED) if (atomic_read(&otg->in_lpm)) return IRQ_HANDLED; return ehci_irq(hcd); } #ifdef CONFIG_PM static int ehci_msm_bus_suspend(struct usb_hcd *hcd) { int ret; struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); struct device *dev = hcd->self.controller; ret = ehci_bus_suspend(hcd); if (ret) { pr_err("ehci_bus suspend faield\n"); return ret; } if (PHY_TYPE(mhcd->pdata->phy_info) == USB_PHY_INTEGRATED) ret = usb_phy_set_suspend(mhcd->xceiv, 1); else ret = usb_lpm_enter(hcd); pm_runtime_put_noidle(dev); pm_runtime_suspend(dev); wake_unlock(&mhcd->wlock); return ret; } static int ehci_msm_bus_resume(struct usb_hcd *hcd) { struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); struct device *dev = hcd->self.controller; wake_lock(&mhcd->wlock); pm_runtime_get_noresume(dev); pm_runtime_resume(dev); if (PHY_TYPE(mhcd->pdata->phy_info) == USB_PHY_INTEGRATED) { usb_phy_set_suspend(mhcd->xceiv, 0); } else { /* PMIC serial phy */ usb_lpm_exit(hcd); if (cancel_work_sync(&(mhcd->lpm_exit_work))) usb_lpm_exit_w(&mhcd->lpm_exit_work); } return ehci_bus_resume(hcd); } #else #define ehci_msm_bus_suspend NULL #define ehci_msm_bus_resume NULL #endif /* CONFIG_PM */ static int ehci_msm_reset(struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); int retval; ehci->caps = USB_CAPLENGTH; ehci->regs = USB_CAPLENGTH + HC_LENGTH(ehci, ehci_readl(ehci, &ehci->caps->hc_capbase)); /* cache the data to minimize the chip reads*/ ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); retval = ehci_init(hcd); if (retval) return retval; hcd->has_tt = 1; ehci->sbrn = HCD_USB2; retval = ehci_reset(ehci); /* SW workaround for USB stability issues*/ writel(0x0, USB_AHB_MODE); writel(0x0, USB_AHB_BURST); return retval; } #define PTS_VAL(x) (PHY_TYPE(x) == USB_PHY_SERIAL_PMIC) ? PORTSC_PTS_SERIAL : \ PORTSC_PTS_ULPI static int ehci_msm_run(struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); int retval = 0; int port = HCS_N_PORTS(ehci->hcs_params); u32 __iomem *reg_ptr; u32 hcc_params; struct msm_usb_host_platform_data *pdata = mhcd->pdata; hcd->uses_new_polling = 1; set_bit(HCD_FLAG_POLL_RH, &hcd->flags); /* set hostmode */ reg_ptr = (u32 __iomem *)(((u8 __iomem *)ehci->regs) + USBMODE); ehci_writel(ehci, (USBMODE_VBUS | USBMODE_SDIS), reg_ptr); /* port configuration - phy, port speed, port power, port enable */ while (port--) ehci_writel(ehci, (PTS_VAL(pdata->phy_info) | PORT_POWER | PORT_PE), &ehci->regs->port_status[port]); ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next); hcc_params = ehci_readl(ehci, &ehci->caps->hcc_params); if (HCC_64BIT_ADDR(hcc_params)) ehci_writel(ehci, 0, &ehci->regs->segment); ehci->command &= ~(CMD_LRESET|CMD_IAAD|CMD_PSE|CMD_ASE|CMD_RESET); ehci->command |= CMD_RUN; ehci_writel(ehci, ehci->command, &ehci->regs->command); ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ ehci->rh_state = EHCI_RH_RUNNING; /*Enable appropriate Interrupts*/ ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); return retval; } static struct hc_driver msm_hc_driver = { .description = hcd_name, .product_desc = "Qualcomm On-Chip EHCI Host Controller", .hcd_priv_size = sizeof(struct msmusb_hcd), /* * generic hardware linkage */ .irq = ehci_msm_irq, .flags = HCD_USB2, .reset = ehci_msm_reset, .start = ehci_msm_run, .stop = ehci_stop, .shutdown = ehci_shutdown, /* * managing i/o requests and associated device resources */ .urb_enqueue = ehci_urb_enqueue, .urb_dequeue = ehci_urb_dequeue, .endpoint_disable = ehci_endpoint_disable, /* * scheduling support */ .get_frame_number = ehci_get_frame, /* * root hub support */ .hub_status_data = ehci_hub_status_data, .hub_control = ehci_hub_control, .bus_suspend = ehci_msm_bus_suspend, .bus_resume = ehci_msm_bus_resume, .relinquish_port = ehci_relinquish_port, .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, }; static void msm_hsusb_request_host(void *handle, int request) { struct msmusb_hcd *mhcd = handle; struct usb_hcd *hcd = mhcd_to_hcd(mhcd); struct msm_usb_host_platform_data *pdata = mhcd->pdata; struct msm_otg *otg = container_of(mhcd->xceiv, struct msm_otg, phy); #ifdef CONFIG_USB_OTG struct usb_device *udev = hcd->self.root_hub; #endif struct device *dev = hcd->self.controller; switch (request) { #ifdef CONFIG_USB_OTG case REQUEST_HNP_SUSPEND: /* disable Root hub auto suspend. As hardware is configured * for peripheral mode, mark hardware is not available. */ if (PHY_TYPE(pdata->phy_info) == USB_PHY_INTEGRATED) { pm_runtime_disable(&udev->dev); /* Mark root hub as disconnected. This would * protect suspend/resume via sysfs. */ udev->state = USB_STATE_NOTATTACHED; clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); hcd->state = HC_STATE_HALT; pm_runtime_put_noidle(dev); pm_runtime_suspend(dev); } break; case REQUEST_HNP_RESUME: if (PHY_TYPE(pdata->phy_info) == USB_PHY_INTEGRATED) { pm_runtime_get_noresume(dev); pm_runtime_resume(dev); disable_irq(hcd->irq); ehci_msm_reset(hcd); ehci_msm_run(hcd); set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); pm_runtime_enable(&udev->dev); udev->state = USB_STATE_CONFIGURED; enable_irq(hcd->irq); } break; #endif case REQUEST_RESUME: usb_hcd_resume_root_hub(hcd); break; case REQUEST_START: if (mhcd->running) break; pm_runtime_get_noresume(dev); pm_runtime_resume(dev); wake_lock(&mhcd->wlock); msm_xusb_pm_qos_update(mhcd, 1); msm_xusb_enable_clks(mhcd); if (PHY_TYPE(pdata->phy_info) == USB_PHY_INTEGRATED) if (otg->set_clk) otg->set_clk(mhcd->xceiv, 1); if (pdata->vbus_power) pdata->vbus_power(pdata->phy_info, 1); if (pdata->config_gpio) pdata->config_gpio(1); usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); mhcd->running = 1; if (PHY_TYPE(pdata->phy_info) == USB_PHY_INTEGRATED) if (otg->set_clk) otg->set_clk(mhcd->xceiv, 0); break; case REQUEST_STOP: if (!mhcd->running) break; mhcd->running = 0; /* come out of lpm before deregistration */ if (PHY_TYPE(pdata->phy_info) == USB_PHY_SERIAL_PMIC) { usb_lpm_exit(hcd); if (cancel_work_sync(&(mhcd->lpm_exit_work))) usb_lpm_exit_w(&mhcd->lpm_exit_work); } usb_remove_hcd(hcd); if (pdata->config_gpio) pdata->config_gpio(0); if (pdata->vbus_power) pdata->vbus_power(pdata->phy_info, 0); msm_xusb_disable_clks(mhcd); wake_lock_timeout(&mhcd->wlock, HZ/2); msm_xusb_pm_qos_update(mhcd, 0); pm_runtime_put_noidle(dev); pm_runtime_suspend(dev); break; } } static void msm_hsusb_otg_work(struct work_struct *work) { struct msmusb_hcd *mhcd; mhcd = container_of(work, struct msmusb_hcd, otg_work); msm_hsusb_request_host((void *)mhcd, mhcd->flags); } static void msm_hsusb_start_host(struct usb_bus *bus, int start) { struct usb_hcd *hcd = bus_to_hcd(bus); struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); mhcd->flags = start; if (in_interrupt()) schedule_work(&mhcd->otg_work); else msm_hsusb_request_host((void *)mhcd, mhcd->flags); } static int msm_xusb_init_phy(struct msmusb_hcd *mhcd) { int ret = -ENODEV; struct usb_hcd *hcd = mhcd_to_hcd(mhcd); struct msm_usb_host_platform_data *pdata = mhcd->pdata; switch (PHY_TYPE(pdata->phy_info)) { case USB_PHY_INTEGRATED: ret = 0; case USB_PHY_SERIAL_PMIC: msm_xusb_enable_clks(mhcd); writel(0, USB_USBINTR); ret = msm_fsusb_rpc_init(&mhcd->otg_ops); if (!ret) msm_fsusb_init_phy(); msm_xusb_disable_clks(mhcd); break; default: pr_err("%s: undefined phy type ( %X ) \n", __func__, pdata->phy_info); } return ret; } static int msm_xusb_rpc_close(struct msmusb_hcd *mhcd) { int retval = -ENODEV; struct msm_usb_host_platform_data *pdata = mhcd->pdata; switch (PHY_TYPE(pdata->phy_info)) { case USB_PHY_INTEGRATED: if (!mhcd->xceiv) retval = msm_hsusb_rpc_close(); break; case USB_PHY_SERIAL_PMIC: retval = msm_fsusb_reset_phy(); msm_fsusb_rpc_deinit(); break; default: pr_err("%s: undefined phy type ( %X ) \n", __func__, pdata->phy_info); } return retval; } static int msm_xusb_init_host(struct platform_device *pdev, struct msmusb_hcd *mhcd) { int ret = 0; struct msm_otg *otg; struct usb_hcd *hcd = mhcd_to_hcd(mhcd); struct ehci_hcd *ehci = hcd_to_ehci(hcd); struct msm_usb_host_platform_data *pdata = mhcd->pdata; switch (PHY_TYPE(pdata->phy_info)) { case USB_PHY_INTEGRATED: msm_hsusb_rpc_connect(); if (pdata->vbus_init) pdata->vbus_init(1); /* VBUS might be present. Turn off vbus */ if (pdata->vbus_power) pdata->vbus_power(pdata->phy_info, 0); INIT_WORK(&mhcd->otg_work, msm_hsusb_otg_work); mhcd->xceiv = usb_get_transceiver(); if (!mhcd->xceiv) return -ENODEV; otg = container_of(mhcd->xceiv, struct msm_otg, phy); hcd->regs = otg->regs; otg->start_host = msm_hsusb_start_host; ret = otg_set_host(mhcd->xceiv->otg, &hcd->self); ehci->transceiver = mhcd->xceiv; break; case USB_PHY_SERIAL_PMIC: hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); if (!hcd->regs) return -EFAULT; /* get usb clocks */ mhcd->alt_core_clk = clk_get(&pdev->dev, "alt_core_clk"); if (IS_ERR(mhcd->alt_core_clk)) { iounmap(hcd->regs); return PTR_ERR(mhcd->alt_core_clk); } mhcd->iface_clk = clk_get(&pdev->dev, "iface_clk"); if (IS_ERR(mhcd->iface_clk)) { iounmap(hcd->regs); clk_put(mhcd->alt_core_clk); return PTR_ERR(mhcd->iface_clk); } mhcd->otg_ops.request = msm_hsusb_request_host; mhcd->otg_ops.handle = (void *) mhcd; ret = msm_xusb_init_phy(mhcd); if (ret < 0) { iounmap(hcd->regs); clk_put(mhcd->alt_core_clk); clk_put(mhcd->iface_clk); } break; default: pr_err("phy type is bad\n"); } return ret; } static int __devinit ehci_msm_probe(struct platform_device *pdev) { struct usb_hcd *hcd; struct resource *res; struct msm_usb_host_platform_data *pdata; int retval; struct msmusb_hcd *mhcd; hcd = usb_create_hcd(&msm_hc_driver, &pdev->dev, dev_name(&pdev->dev)); if (!hcd) return -ENOMEM; hcd_to_bus(hcd)->skip_resume = true; hcd->irq = platform_get_irq(pdev, 0); if (hcd->irq < 0) { usb_put_hcd(hcd); return hcd->irq; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { usb_put_hcd(hcd); return -ENODEV; } hcd->rsrc_start = res->start; hcd->rsrc_len = resource_size(res); mhcd = hcd_to_mhcd(hcd); spin_lock_init(&mhcd->lock); mhcd->in_lpm = 0; mhcd->running = 0; device_init_wakeup(&pdev->dev, 1); pdata = pdev->dev.platform_data; if (PHY_TYPE(pdata->phy_info) == USB_PHY_UNDEFINED) { usb_put_hcd(hcd); return -ENODEV; } hcd->power_budget = pdata->power_budget; mhcd->pdata = pdata; INIT_WORK(&mhcd->lpm_exit_work, usb_lpm_exit_w); wake_lock_init(&mhcd->wlock, WAKE_LOCK_SUSPEND, dev_name(&pdev->dev)); pdata->ebi1_clk = clk_get(&pdev->dev, "core_clk"); if (IS_ERR(pdata->ebi1_clk)) pdata->ebi1_clk = NULL; else clk_set_rate(pdata->ebi1_clk, INT_MAX); retval = msm_xusb_init_host(pdev, mhcd); if (retval < 0) { wake_lock_destroy(&mhcd->wlock); usb_put_hcd(hcd); clk_put(pdata->ebi1_clk); } pm_runtime_enable(&pdev->dev); return retval; } static void msm_xusb_uninit_host(struct msmusb_hcd *mhcd) { struct usb_hcd *hcd = mhcd_to_hcd(mhcd); struct msm_usb_host_platform_data *pdata = mhcd->pdata; switch (PHY_TYPE(pdata->phy_info)) { case USB_PHY_INTEGRATED: if (pdata->vbus_init) pdata->vbus_init(0); hcd_to_ehci(hcd)->transceiver = NULL; otg_set_host(mhcd->xceiv->otg, NULL); usb_put_transceiver(mhcd->xceiv); cancel_work_sync(&mhcd->otg_work); break; case USB_PHY_SERIAL_PMIC: iounmap(hcd->regs); clk_put(mhcd->alt_core_clk); clk_put(mhcd->iface_clk); msm_fsusb_reset_phy(); msm_fsusb_rpc_deinit(); break; default: pr_err("phy type is bad\n"); } } static int __exit ehci_msm_remove(struct platform_device *pdev) { struct usb_hcd *hcd = platform_get_drvdata(pdev); struct msmusb_hcd *mhcd = hcd_to_mhcd(hcd); struct msm_usb_host_platform_data *pdata; int retval = 0; pdata = pdev->dev.platform_data; device_init_wakeup(&pdev->dev, 0); msm_hsusb_request_host((void *)mhcd, REQUEST_STOP); msm_xusb_uninit_host(mhcd); retval = msm_xusb_rpc_close(mhcd); wake_lock_destroy(&mhcd->wlock); usb_put_hcd(hcd); clk_put(pdata->ebi1_clk); pm_runtime_disable(&pdev->dev); pm_runtime_set_suspended(&pdev->dev); return retval; } static int ehci_msm_runtime_suspend(struct device *dev) { dev_dbg(dev, "pm_runtime: suspending...\n"); return 0; } static int ehci_msm_runtime_resume(struct device *dev) { dev_dbg(dev, "pm_runtime: resuming...\n"); return 0; } static int ehci_msm_runtime_idle(struct device *dev) { dev_dbg(dev, "pm_runtime: idling...\n"); return 0; } static const struct dev_pm_ops ehci_msm_dev_pm_ops = { .runtime_suspend = ehci_msm_runtime_suspend, .runtime_resume = ehci_msm_runtime_resume, .runtime_idle = ehci_msm_runtime_idle }; static struct platform_driver ehci_msm_driver = { .probe = ehci_msm_probe, .remove = __exit_p(ehci_msm_remove), .driver = {.name = "msm_hsusb_host", .pm = &ehci_msm_dev_pm_ops, }, };