| /* |
| * EHCI-compliant USB host controller driver for NVIDIA Tegra SoCs |
| * |
| * Copyright (c) 2010 Google, Inc. |
| * Copyright (c) 2009-2013 NVIDIA CORPORATION. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or (at your |
| * option) any later version. |
| * |
| * 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. |
| * |
| */ |
| |
| #include <linux/err.h> |
| #include <linux/platform_device.h> |
| #include <linux/platform_data/tegra_usb.h> |
| #include <linux/irq.h> |
| #include <linux/usb/otg.h> |
| #include <linux/gpio.h> |
| #include <linux/of.h> |
| #include <linux/delay.h> |
| #include <linux/of_gpio.h> |
| #include <linux/pm_runtime.h> |
| #include <linux/tegra-soc.h> |
| |
| #include <linux/usb/tegra_usb_phy.h> |
| |
| #include <mach/pm_domains.h> |
| #include <linux/pm_qos.h> |
| |
| /* HACK! This needs to come from DT */ |
| #include "../../../arch/arm/mach-tegra/iomap.h" |
| |
| #if 0 |
| #define EHCI_DBG(stuff...) pr_info("ehci-tegra: " stuff) |
| #else |
| #define EHCI_DBG(stuff...) do {} while (0) |
| #endif |
| |
| static const char driver_name[] = "tegra-ehci"; |
| |
| #define TEGRA_USB_DMA_ALIGN 32 |
| |
| #define HOSTPC_REG_OFFSET 0x1b4 |
| |
| #define HOSTPC1_DEVLC_STS (1 << 28) |
| #define HOSTPC1_DEVLC_NYT_ASUS 1 |
| #define TEGRA_STREAM_DISABLE 0x1f8 |
| #define TEGRA_STREAM_DISABLE_OFFSET (1 << 4) |
| |
| struct tegra_ehci_hcd { |
| struct ehci_hcd *ehci; |
| struct tegra_usb_phy *phy; |
| struct usb_phy *transceiver; |
| struct mutex sync_lock; |
| bool port_resuming; |
| unsigned int irq; |
| bool bus_suspended_fail; |
| bool unaligned_dma_buf_supported; |
| bool has_hostpc; |
| #ifdef CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ |
| bool boost_enable; |
| bool boost_requested; |
| bool cpu_boost_in_work; |
| struct delayed_work boost_cpu_freq_work; |
| struct pm_qos_request boost_cpu_freq_req; |
| #endif |
| }; |
| |
| struct dma_align_buffer { |
| void *kmalloc_ptr; |
| void *old_xfer_buffer; |
| u8 data[0]; |
| }; |
| |
| static struct usb_phy *get_usb_phy(struct tegra_usb_phy *x) |
| { |
| return (struct usb_phy *)x; |
| } |
| |
| static void free_align_buffer(struct urb *urb, struct usb_hcd *hcd) |
| { |
| struct dma_align_buffer *temp; |
| struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); |
| |
| if (tegra->unaligned_dma_buf_supported) |
| return; |
| |
| if (!(urb->transfer_flags & URB_ALIGNED_TEMP_BUFFER)) |
| return; |
| |
| temp = container_of(urb->transfer_buffer, |
| struct dma_align_buffer, data); |
| |
| /* In transaction, DMA from Device */ |
| if (usb_urb_dir_in(urb)) |
| memcpy(temp->old_xfer_buffer, temp->data, |
| urb->transfer_buffer_length); |
| |
| urb->transfer_buffer = temp->old_xfer_buffer; |
| urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; |
| kfree(temp->kmalloc_ptr); |
| } |
| |
| static int alloc_align_buffer(struct urb *urb, gfp_t mem_flags, |
| struct usb_hcd *hcd) |
| { |
| struct dma_align_buffer *temp, *kmalloc_ptr; |
| size_t kmalloc_size; |
| struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); |
| |
| if (tegra->unaligned_dma_buf_supported) |
| return 0; |
| |
| if (urb->num_sgs || urb->sg || |
| urb->transfer_buffer_length == 0 || |
| !((uintptr_t)urb->transfer_buffer & (TEGRA_USB_DMA_ALIGN - 1))) |
| return 0; |
| |
| /* Allocate a buffer with enough padding for alignment */ |
| kmalloc_size = urb->transfer_buffer_length + |
| sizeof(struct dma_align_buffer) + TEGRA_USB_DMA_ALIGN - 1; |
| kmalloc_ptr = kmalloc(kmalloc_size, mem_flags); |
| |
| if (!kmalloc_ptr) |
| return -ENOMEM; |
| |
| /* Position our struct dma_align_buffer such that data is aligned */ |
| temp = PTR_ALIGN(kmalloc_ptr + 1, TEGRA_USB_DMA_ALIGN) - 1; |
| temp->kmalloc_ptr = kmalloc_ptr; |
| temp->old_xfer_buffer = urb->transfer_buffer; |
| /* OUT transaction, DMA to Device */ |
| if (!usb_urb_dir_in(urb)) |
| memcpy(temp->data, urb->transfer_buffer, |
| urb->transfer_buffer_length); |
| |
| urb->transfer_buffer = temp->data; |
| urb->transfer_flags |= URB_ALIGNED_TEMP_BUFFER; |
| |
| return 0; |
| } |
| |
| static int tegra_ehci_map_urb_for_dma(struct usb_hcd *hcd, |
| struct urb *urb, gfp_t mem_flags) |
| { |
| int ret; |
| |
| ret = alloc_align_buffer(urb, mem_flags, hcd); |
| if (ret) |
| return ret; |
| |
| ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); |
| |
| if (ret) |
| free_align_buffer(urb, hcd); |
| |
| return ret; |
| } |
| |
| static void tegra_ehci_unmap_urb_for_dma(struct usb_hcd *hcd, |
| struct urb *urb) |
| { |
| usb_hcd_unmap_urb_for_dma(hcd, urb); |
| free_align_buffer(urb, hcd); |
| } |
| |
| #ifdef CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ |
| static void tegra_ehci_boost_cpu_frequency_work(struct work_struct *work) |
| { |
| struct tegra_ehci_hcd *tegra = container_of(work, |
| struct tegra_ehci_hcd, boost_cpu_freq_work.work); |
| if (tegra->cpu_boost_in_work) { |
| tegra->boost_requested = true; |
| if (tegra->boost_enable) |
| pm_qos_update_request( |
| &tegra->boost_cpu_freq_req, |
| (s32)CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ * 1000); |
| } |
| } |
| #endif |
| |
| static irqreturn_t tegra_ehci_irq(struct usb_hcd *hcd) |
| { |
| struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); |
| struct ehci_hcd *ehci = hcd_to_ehci(hcd); |
| irqreturn_t irq_status; |
| |
| spin_lock(&ehci->lock); |
| irq_status = tegra_usb_phy_irq(tegra->phy); |
| if (irq_status == IRQ_NONE) { |
| spin_unlock(&ehci->lock); |
| return irq_status; |
| } |
| if (tegra_usb_phy_pmc_wakeup(tegra->phy)) { |
| ehci_dbg(ehci, "pmc wakeup detected\n"); |
| usb_hcd_resume_root_hub(hcd); |
| spin_unlock(&ehci->lock); |
| return irq_status; |
| } |
| spin_unlock(&ehci->lock); |
| |
| EHCI_DBG("%s() cmd = 0x%x, int_sts = 0x%x, portsc = 0x%x\n", __func__, |
| ehci_readl(ehci, &ehci->regs->command), |
| ehci_readl(ehci, &ehci->regs->status), |
| ehci_readl(ehci, &ehci->regs->port_status[0])); |
| |
| irq_status = ehci_irq(hcd); |
| |
| if (ehci->controller_remote_wakeup) { |
| ehci->controller_remote_wakeup = false; |
| tegra_usb_phy_pre_resume(tegra->phy, true); |
| tegra->port_resuming = 1; |
| } |
| return irq_status; |
| } |
| |
| |
| static int tegra_ehci_hub_control( |
| struct usb_hcd *hcd, |
| u16 typeReq, |
| u16 wValue, |
| u16 wIndex, |
| char *buf, |
| u16 wLength |
| ) |
| { |
| struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); |
| struct ehci_hcd *ehci = hcd_to_ehci(hcd); |
| int retval = 0; |
| u32 __iomem *status_reg; |
| |
| if (!tegra_usb_phy_hw_accessible(tegra->phy)) { |
| if (buf) |
| memset(buf, 0, wLength); |
| return retval; |
| } |
| |
| /* Do tegra phy specific actions based on the type request */ |
| switch (typeReq) { |
| case GetPortStatus: |
| if (tegra->port_resuming) { |
| u32 cmd; |
| int delay = ehci->reset_done[wIndex-1] - jiffies; |
| /* Sometimes it seems we get called too soon... In that case, wait.*/ |
| if (delay > 0) { |
| ehci_dbg(ehci, "GetPortStatus called too soon, waiting %dms...\n", delay); |
| mdelay(jiffies_to_msecs(delay)); |
| } |
| status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; |
| /* Ensure the port PORT_SUSPEND and PORT_RESUME has cleared */ |
| if (handshake(ehci, status_reg, (PORT_SUSPEND | PORT_RESUME), 0, 25000)) { |
| EHCI_DBG("%s: timeout waiting for SUSPEND to clear\n", __func__); |
| } |
| tegra_usb_phy_post_resume(tegra->phy); |
| tegra->port_resuming = 0; |
| /* If run bit is not set by now enable it */ |
| cmd = ehci_readl(ehci, &ehci->regs->command); |
| if (!(cmd & CMD_RUN)) { |
| cmd |= CMD_RUN; |
| ehci->command |= CMD_RUN; |
| ehci_writel(ehci, cmd, &ehci->regs->command); |
| } |
| /* Now we can safely re-enable irqs */ |
| ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); |
| } |
| break; |
| case ClearPortFeature: |
| if (wValue == USB_PORT_FEAT_SUSPEND) { |
| tegra_usb_phy_pre_resume(tegra->phy, false); |
| tegra->port_resuming = 1; |
| } else if (wValue == USB_PORT_FEAT_ENABLE) { |
| u32 temp; |
| temp = ehci_readl(ehci, &ehci->regs->port_status[0]) & ~PORT_RWC_BITS; |
| ehci_writel(ehci, temp & ~PORT_PE, &ehci->regs->port_status[0]); |
| return retval; |
| } |
| break; |
| case SetPortFeature: |
| if (wValue == USB_PORT_FEAT_SUSPEND) |
| tegra_usb_phy_pre_suspend(tegra->phy); |
| break; |
| } |
| |
| /* handle ehci hub control request */ |
| retval = ehci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength); |
| |
| /* do tegra phy specific actions based on the type request */ |
| if (!retval) { |
| switch (typeReq) { |
| case SetPortFeature: |
| if (wValue == USB_PORT_FEAT_SUSPEND) { |
| tegra_usb_phy_post_suspend(tegra->phy); |
| } else if (wValue == USB_PORT_FEAT_RESET) { |
| if (wIndex == 1) |
| tegra_usb_phy_bus_reset(tegra->phy); |
| } else if (wValue == USB_PORT_FEAT_POWER) { |
| if (wIndex == 1) |
| tegra_usb_phy_port_power(tegra->phy); |
| } |
| break; |
| case ClearPortFeature: |
| if (wValue == USB_PORT_FEAT_SUSPEND) { |
| /* tegra USB controller needs 25 ms to resume the port */ |
| ehci->reset_done[wIndex-1] = jiffies + msecs_to_jiffies(25); |
| } |
| break; |
| } |
| } |
| |
| return retval; |
| } |
| |
| static void tegra_ehci_shutdown(struct usb_hcd *hcd) |
| { |
| struct ehci_hcd *ehci = hcd_to_ehci(hcd); |
| struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); |
| struct platform_device *pdev = to_platform_device(hcd->self.controller); |
| struct tegra_usb_platform_data *pdata = dev_get_platdata(&pdev->dev); |
| mutex_lock(&tegra->sync_lock); |
| if (tegra_usb_phy_hw_accessible(tegra->phy)) { |
| spin_lock_irq(&ehci->lock); |
| ehci_silence_controller(ehci); |
| spin_unlock_irq(&ehci->lock); |
| } |
| if (pdata->port_otg) |
| tegra_usb_enable_vbus(tegra->phy, false); |
| mutex_unlock(&tegra->sync_lock); |
| } |
| |
| static int tegra_ehci_setup(struct usb_hcd *hcd) |
| { |
| struct ehci_hcd *ehci = hcd_to_ehci(hcd); |
| struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); |
| int retval; |
| #ifndef CONFIG_ARCH_TEGRA_2x_SOC |
| u32 val; |
| #endif |
| |
| /* EHCI registers start at offset 0x100 */ |
| ehci->caps = hcd->regs + 0x100; |
| |
| ehci->has_hostpc = tegra->has_hostpc; |
| ehci->broken_hostpc_phcd = true; |
| |
| #ifndef CONFIG_ARCH_TEGRA_2x_SOC |
| ehci->has_hostpc = 1; |
| |
| val = readl(hcd->regs + HOSTPC_REG_OFFSET); |
| val &= ~HOSTPC1_DEVLC_STS; |
| val &= ~HOSTPC1_DEVLC_NYT_ASUS; |
| writel(val, hcd->regs + HOSTPC_REG_OFFSET); |
| #endif |
| /* switch to host mode */ |
| hcd->has_tt = 1; |
| |
| retval = ehci_setup(hcd); |
| if (retval) |
| return retval; |
| |
| ehci->controller_remote_wakeup = false; |
| tegra_usb_phy_reset(tegra->phy); |
| |
| #if !defined(CONFIG_ARCH_TEGRA_2x_SOC) |
| if (tegra_platform_is_fpga()) { |
| val = readl(hcd->regs + TEGRA_STREAM_DISABLE); |
| val |= TEGRA_STREAM_DISABLE_OFFSET; |
| writel(val , hcd->regs + TEGRA_STREAM_DISABLE); |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| |
| #ifdef CONFIG_PM |
| static int tegra_ehci_bus_suspend(struct usb_hcd *hcd) |
| { |
| struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); |
| int err = 0; |
| EHCI_DBG("%s() BEGIN\n", __func__); |
| |
| #ifdef CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ |
| tegra->boost_requested = false; |
| if (tegra->boost_enable |
| && pm_qos_request_active(&tegra->boost_cpu_freq_req)) |
| pm_qos_update_request(&tegra->boost_cpu_freq_req, |
| PM_QOS_DEFAULT_VALUE); |
| tegra->cpu_boost_in_work = false; |
| #endif |
| |
| mutex_lock(&tegra->sync_lock); |
| tegra->bus_suspended_fail = false; |
| err = ehci_bus_suspend(hcd); |
| if (err) |
| tegra->bus_suspended_fail = true; |
| else |
| usb_phy_set_suspend(get_usb_phy(tegra->phy), 1); |
| mutex_unlock(&tegra->sync_lock); |
| EHCI_DBG("%s() END\n", __func__); |
| |
| return err; |
| } |
| |
| static int tegra_ehci_bus_resume(struct usb_hcd *hcd) |
| { |
| struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); |
| int err = 0; |
| EHCI_DBG("%s() BEGIN\n", __func__); |
| |
| #ifdef CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ |
| tegra->boost_requested = true; |
| if (pm_qos_request_active(&tegra->boost_cpu_freq_req) |
| && tegra->boost_enable) |
| pm_qos_update_request(&tegra->boost_cpu_freq_req, |
| (s32)CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ * 1000); |
| tegra->cpu_boost_in_work = false; |
| |
| #endif |
| |
| mutex_lock(&tegra->sync_lock); |
| usb_phy_set_suspend(get_usb_phy(tegra->phy), 0); |
| err = ehci_bus_resume(hcd); |
| mutex_unlock(&tegra->sync_lock); |
| EHCI_DBG("%s() END\n", __func__); |
| |
| return err; |
| } |
| #endif |
| |
| static const struct hc_driver tegra_ehci_hc_driver = { |
| .description = hcd_name, |
| .product_desc = "Tegra EHCI Host Controller", |
| .hcd_priv_size = sizeof(struct ehci_hcd), |
| .flags = HCD_USB2 | HCD_MEMORY, |
| |
| /* standard ehci functions */ |
| .start = ehci_run, |
| .stop = ehci_stop, |
| .urb_enqueue = ehci_urb_enqueue, |
| .urb_dequeue = ehci_urb_dequeue, |
| .endpoint_disable = ehci_endpoint_disable, |
| .endpoint_reset = ehci_endpoint_reset, |
| .get_frame_number = ehci_get_frame, |
| .hub_status_data = ehci_hub_status_data, |
| .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, |
| .relinquish_port = ehci_relinquish_port, |
| .port_handed_over = ehci_port_handed_over, |
| |
| /* modified ehci functions for tegra */ |
| .reset = tegra_ehci_setup, |
| .irq = tegra_ehci_irq, |
| .shutdown = tegra_ehci_shutdown, |
| .map_urb_for_dma = tegra_ehci_map_urb_for_dma, |
| .unmap_urb_for_dma = tegra_ehci_unmap_urb_for_dma, |
| .hub_control = tegra_ehci_hub_control, |
| #ifdef CONFIG_PM |
| .bus_suspend = tegra_ehci_bus_suspend, |
| .bus_resume = tegra_ehci_bus_resume, |
| #endif |
| }; |
| |
| static u64 tegra_ehci_dma_mask = DMA_BIT_MASK(32); |
| |
| #ifdef CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ |
| static ssize_t show_boost_enable(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct tegra_ehci_hcd *tegra = dev_get_drvdata(dev); |
| return scnprintf(buf, PAGE_SIZE, "%s\n", |
| tegra->boost_enable ? "Y" : "N"); |
| } |
| |
| |
| static ssize_t store_boost_enable(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct tegra_ehci_hcd *tegra = dev_get_drvdata(dev); |
| bool new_boost; |
| bool old_boost = tegra->boost_enable; |
| |
| if (strtobool(buf, &new_boost) == 0) { |
| tegra->boost_enable = new_boost; |
| if (!old_boost && new_boost |
| && tegra->boost_requested |
| && pm_qos_request_active(&tegra->boost_cpu_freq_req)) |
| pm_qos_update_request( |
| &tegra->boost_cpu_freq_req, |
| (s32)CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ * 1000); |
| else if (old_boost && !new_boost |
| && pm_qos_request_active(&tegra->boost_cpu_freq_req)) |
| pm_qos_update_request(&tegra->boost_cpu_freq_req, |
| PM_QOS_DEFAULT_VALUE); |
| } |
| return count; |
| } |
| static DEVICE_ATTR(boost_enable, 0644, |
| show_boost_enable, store_boost_enable); |
| #endif |
| |
| static int tegra_ehci_probe(struct platform_device *pdev) |
| { |
| struct resource *res; |
| struct usb_hcd *hcd; |
| struct tegra_ehci_hcd *tegra; |
| struct tegra_usb_platform_data *pdata; |
| int err = 0; |
| int irq; |
| int instance = pdev->id; |
| |
| /* 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 = &tegra_ehci_dma_mask; |
| |
| pdata = dev_get_platdata(&pdev->dev); |
| |
| tegra = devm_kzalloc(&pdev->dev, sizeof(struct tegra_ehci_hcd), |
| GFP_KERNEL); |
| if (!tegra) { |
| dev_err(&pdev->dev, "memory alloc failed\n"); |
| return -ENOMEM; |
| } |
| |
| mutex_init(&tegra->sync_lock); |
| |
| hcd = usb_create_hcd(&tegra_ehci_hc_driver, &pdev->dev, |
| dev_name(&pdev->dev)); |
| if (!hcd) { |
| dev_err(&pdev->dev, "unable to create HCD\n"); |
| return -ENOMEM; |
| } |
| |
| platform_set_drvdata(pdev, tegra); |
| |
| #ifdef CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ |
| tegra->boost_requested = false; |
| /* Add boost enable/disable knob */ |
| tegra->boost_enable = true; |
| err = device_create_file(hcd->self.controller, &dev_attr_boost_enable); |
| if (err < 0) |
| goto fail_sysfs; |
| #endif |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| if (!res) { |
| dev_err(&pdev->dev, "failed to get I/O memory\n"); |
| err = -ENXIO; |
| goto fail_io; |
| } |
| hcd->rsrc_start = res->start; |
| hcd->rsrc_len = resource_size(res); |
| hcd->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); |
| if (!hcd->regs) { |
| dev_err(&pdev->dev, "failed to remap I/O memory\n"); |
| err = -ENOMEM; |
| goto fail_io; |
| } |
| |
| /* This is pretty ugly and needs to be fixed when we do only |
| * device-tree probing. Old code relies on the platform_device |
| * numbering that we lack for device-tree-instantiated devices. |
| */ |
| if (instance < 0) { |
| switch (res->start) { |
| case TEGRA_USB_BASE: |
| instance = 0; |
| break; |
| case TEGRA_USB2_BASE: |
| instance = 1; |
| break; |
| case TEGRA_USB3_BASE: |
| instance = 2; |
| break; |
| default: |
| err = -ENODEV; |
| dev_err(&pdev->dev, "unknown usb instance\n"); |
| goto fail_io; |
| } |
| } |
| |
| irq = platform_get_irq(pdev, 0); |
| if (irq < 0) { |
| dev_err(&pdev->dev, "failed to get IRQ\n"); |
| err = -ENODEV; |
| goto fail_io; |
| } |
| tegra->irq = irq; |
| |
| tegra->unaligned_dma_buf_supported = pdata->unaligned_dma_buf_supported; |
| tegra->has_hostpc = pdata->has_hostpc; |
| |
| tegra->phy = tegra_usb_phy_open(pdev); |
| if (IS_ERR(tegra->phy)) { |
| dev_err(&pdev->dev, "failed to open USB phy\n"); |
| err = -ENXIO; |
| goto fail_io; |
| } |
| |
| err = usb_phy_set_suspend(get_usb_phy(tegra->phy), 0); |
| if (err) { |
| dev_err(&pdev->dev, "failed to power on the phy\n"); |
| goto fail_phy; |
| } |
| |
| err = usb_phy_init(get_usb_phy(tegra->phy)); |
| if (err) { |
| dev_err(&pdev->dev, "failed to init the phy\n"); |
| goto fail_phy; |
| } |
| |
| err = usb_add_hcd(hcd, irq, IRQF_SHARED | IRQF_TRIGGER_HIGH); |
| if (err) { |
| dev_err(&pdev->dev, "Failed to add USB HCD, error=%d\n", err); |
| goto fail_phy; |
| } |
| |
| err = enable_irq_wake(tegra->irq); |
| if (err < 0) { |
| dev_warn(&pdev->dev, |
| "Couldn't enable USB host mode wakeup, irq=%d, " |
| "error=%d\n", irq, err); |
| err = 0; |
| tegra->irq = 0; |
| } |
| |
| tegra->ehci = hcd_to_ehci(hcd); |
| |
| if (pdata->port_otg) { |
| tegra->transceiver = |
| devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2); |
| if (!IS_ERR_OR_NULL(tegra->transceiver)) |
| otg_set_host(tegra->transceiver->otg, &hcd->self); |
| } |
| |
| tegra_pd_add_device(&pdev->dev); |
| pm_runtime_enable(&pdev->dev); |
| |
| #ifdef CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ |
| INIT_DELAYED_WORK(&tegra->boost_cpu_freq_work, |
| tegra_ehci_boost_cpu_frequency_work); |
| pm_qos_add_request(&tegra->boost_cpu_freq_req, PM_QOS_CPU_FREQ_MIN, |
| PM_QOS_DEFAULT_VALUE); |
| schedule_delayed_work(&tegra->boost_cpu_freq_work, 4000); |
| tegra->cpu_boost_in_work = true; |
| #endif |
| |
| return err; |
| |
| fail_phy: |
| usb_phy_shutdown(get_usb_phy(tegra->phy)); |
| fail_io: |
| #ifdef CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ |
| device_remove_file(hcd->self.controller, &dev_attr_boost_enable); |
| fail_sysfs: |
| #endif |
| usb_put_hcd(hcd); |
| |
| return err; |
| } |
| |
| |
| #ifdef CONFIG_PM |
| static int tegra_ehci_resume(struct platform_device *pdev) |
| { |
| struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); |
| struct tegra_usb_platform_data *pdata = dev_get_platdata(&pdev->dev); |
| if (pdata->u_data.host.turn_off_vbus_on_lp0) |
| tegra_usb_enable_vbus(tegra->phy, true); |
| return usb_phy_set_suspend(get_usb_phy(tegra->phy), 0); |
| } |
| |
| static int tegra_ehci_suspend(struct platform_device *pdev, pm_message_t state) |
| { |
| struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); |
| struct tegra_usb_platform_data *pdata = dev_get_platdata(&pdev->dev); |
| int err; |
| |
| /* bus suspend could have failed because of remote wakeup resume */ |
| if (tegra->bus_suspended_fail) |
| return -EBUSY; |
| else { |
| err = usb_phy_set_suspend(get_usb_phy(tegra->phy), 1); |
| if (pdata->u_data.host.turn_off_vbus_on_lp0) { |
| tegra_usb_enable_vbus(tegra->phy, false); |
| tegra_usb_phy_pmc_disable(tegra->phy); |
| } |
| return err; |
| } |
| } |
| #endif |
| |
| static int tegra_ehci_remove(struct platform_device *pdev) |
| { |
| struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); |
| struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); |
| struct usb_device *rhdev = NULL; |
| struct tegra_usb_platform_data *pdata; |
| unsigned long timeout = 0; |
| |
| #ifdef CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ |
| cancel_delayed_work_sync(&tegra->boost_cpu_freq_work); |
| tegra->cpu_boost_in_work = false; |
| pm_qos_remove_request(&tegra->boost_cpu_freq_req); |
| #endif |
| rhdev = hcd->self.root_hub; |
| pdata = dev_get_platdata(&pdev->dev); |
| |
| if (!IS_ERR_OR_NULL(tegra->transceiver)) |
| otg_set_host(tegra->transceiver->otg, NULL); |
| |
| if (tegra->irq) |
| disable_irq_wake(tegra->irq); |
| |
| /* Make sure phy is powered ON to access USB register */ |
| if(!tegra_usb_phy_hw_accessible(tegra->phy)) |
| tegra_usb_phy_power_on(tegra->phy); |
| |
| if (pdata->port_otg) { |
| timeout = jiffies + 5 * HZ; |
| /* wait for devices connected to root hub to disconnect*/ |
| while (rhdev && usb_hub_find_child(rhdev, 1)) { |
| /* wait for any control packets |
| sent to root hub to complete */ |
| if (time_after(jiffies, timeout)) |
| break; |
| msleep(20); |
| cpu_relax(); |
| } |
| } |
| |
| usb_remove_hcd(hcd); |
| usb_put_hcd(hcd); |
| tegra_usb_phy_power_off(tegra->phy); |
| usb_phy_shutdown(get_usb_phy(tegra->phy)); |
| #ifdef CONFIG_TEGRA_EHCI_BOOST_CPU_FREQ |
| device_remove_file(hcd->self.controller, &dev_attr_boost_enable); |
| #endif |
| |
| tegra_pd_remove_device(&pdev->dev); |
| |
| return 0; |
| } |
| |
| static void tegra_ehci_hcd_shutdown(struct platform_device *pdev) |
| { |
| struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); |
| struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); |
| |
| if (hcd->driver->shutdown) |
| hcd->driver->shutdown(hcd); |
| } |
| |
| static struct of_device_id tegra_ehci_of_match[] = { |
| { .compatible = "nvidia,tegra20-ehci", }, |
| { }, |
| }; |
| |
| static struct platform_driver tegra_ehci_driver = { |
| .probe = tegra_ehci_probe, |
| .remove = tegra_ehci_remove, |
| .shutdown = tegra_ehci_hcd_shutdown, |
| #ifdef CONFIG_PM |
| .suspend = tegra_ehci_suspend, |
| .resume = tegra_ehci_resume, |
| #endif |
| .driver = { |
| .name = driver_name, |
| .of_match_table = tegra_ehci_of_match, |
| } |
| }; |