diff options
author | Seshendra Gadagottu <sgadagottu@nvidia.com> | 2011-01-11 11:41:09 -0800 |
---|---|---|
committer | Varun Colbert <vcolbert@nvidia.com> | 2011-02-10 15:41:11 -0800 |
commit | 8df80194dc456a45fc8245de57bd9b46d2cf16a8 (patch) | |
tree | 78a310256698305e2ac7dae9df58b39fd5cacec7 /drivers | |
parent | 57a3407736092650cbed228fdf9264e140378762 (diff) |
tegra hsic: Adding USB hsic driver support to K36
Adding hsic funtionality to USB2 instance.
Add the changes required for hsic functionality and
power management.
BUG 756184
Change-Id: Ife8a1fc6ea95b15f66d840b1565d858ee25d5ded
Reviewed-on: http://git-master/r/15192
Tested-by: Seshendra Gadagottu <sgadagottu@nvidia.com>
Reviewed-by: Michael Hsu <mhsu@nvidia.com>
Tested-by: Michael Hsu <mhsu@nvidia.com>
Reviewed-by: Shail Dave <sdave@nvidia.com>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/usb/host/ehci-tegra.c | 270 |
1 files changed, 227 insertions, 43 deletions
diff --git a/drivers/usb/host/ehci-tegra.c b/drivers/usb/host/ehci-tegra.c index 5765496218a1..a6dd7ac7dc42 100644 --- a/drivers/usb/host/ehci-tegra.c +++ b/drivers/usb/host/ehci-tegra.c @@ -2,7 +2,7 @@ * EHCI-compliant USB host controller driver for NVIDIA Tegra SoCs * * Copyright (C) 2010 Google, Inc. - * Copyright (C) 2009 NVIDIA Corporation + * Copyright (C) 2009 - 2011 NVIDIA Corporation * * 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 @@ -57,6 +57,7 @@ struct tegra_ehci_hcd { int port_resuming; struct tegra_ehci_context context; int power_down_on_bus_suspend; + struct delayed_work work; }; static void tegra_ehci_power_up(struct usb_hcd *hcd) @@ -89,11 +90,20 @@ static int tegra_ehci_hub_control( ) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); - struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); - u32 __iomem *status_reg; - u32 temp; + int ports = HCS_N_PORTS(ehci->hcs_params); + u32 __iomem *status_reg = &ehci->regs->port_status[ + (wIndex & 0xff) - 1]; + u32 temp, status; unsigned long flags; int retval = 0; + unsigned selector; + struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); + bool hsic = false; + + if (tegra->phy->instance == 1) { + struct tegra_ulpi_config *config = tegra->phy->config; + hsic = (config->inf_type == TEGRA_USB_UHSIC); + } status_reg = &ehci->regs->port_status[(wIndex & 0xff) - 1]; @@ -108,18 +118,14 @@ static int tegra_ehci_hub_control( temp = ehci_readl(ehci, status_reg); ehci_writel(ehci, (temp & ~PORT_RWC_BITS) & ~PORT_PE, status_reg); goto done; - } - - else if (typeReq == GetPortStatus) { + } else if (typeReq == GetPortStatus) { temp = ehci_readl(ehci, status_reg); if (tegra->port_resuming && !(temp & PORT_SUSPEND)) { /* resume completed */ tegra->port_resuming = 0; tegra_usb_phy_postresume(tegra->phy); } - } - - else if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { + } else if (typeReq == SetPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { temp = ehci_readl(ehci, status_reg); if ((temp & PORT_PE) == 0 || (temp & PORT_RESET) != 0) { retval = -EPIPE; @@ -127,9 +133,9 @@ static int tegra_ehci_hub_control( } /* After above check the port must be connected. - * Set appropriate bit thus could put phy into low power - * mode if we have hostpc feature - */ + * Set appropriate bit thus could put phy into low power + * mode if we have hostpc feature + */ temp &= ~PORT_WKCONN_E; temp |= PORT_WKDISC_E | PORT_WKOC_E; ehci_writel(ehci, temp | PORT_SUSPEND, status_reg); @@ -141,12 +147,13 @@ static int tegra_ehci_hub_control( } /* - * Tegra host controller will time the resume operation to clear the bit - * when the port control state switches to HS or FS Idle. This behavior - * is different from EHCI where the host controller driver is required - * to set this bit to a zero after the resume duration is timed in the - * driver. - */ + * Tegra host controller will time the resume operation to clear the bit + * when the port control state switches to HS or FS Idle. This behavior + * is different from EHCI where the host controller driver is required + * to set this bit to a zero after the resume duration is timed in the + * driver. + */ + else if (typeReq == ClearPortFeature && wValue == USB_PORT_FEAT_SUSPEND) { temp = ehci_readl(ehci, status_reg); if ((temp & PORT_RESET) || !(temp & PORT_PE)) { @@ -183,6 +190,65 @@ static int tegra_ehci_hub_control( goto done; } + /* Handle port reset here */ + if ((hsic) && (typeReq == SetPortFeature) && + ((wValue == USB_PORT_FEAT_RESET) || (wValue == USB_PORT_FEAT_POWER))) { + selector = wIndex >> 8; + wIndex &= 0xff; + if (!wIndex || wIndex > ports) { + retval = -EPIPE; + goto done; + } + wIndex--; + status = 0; + temp = ehci_readl(ehci, status_reg); + if (temp & PORT_OWNER) + goto done; + temp &= ~PORT_RWC_BITS; + + switch (wValue) { + case USB_PORT_FEAT_RESET: + { + if (temp & PORT_RESUME) { + retval = -EPIPE; + goto done; + } + /* line status bits may report this as low speed, + * which can be fine if this root hub has a + * transaction translator built in. + */ + if ((temp & (PORT_PE|PORT_CONNECT)) == PORT_CONNECT + && !ehci_is_TDI(ehci) && PORT_USB11 (temp)) { + ehci_dbg (ehci, "port %d low speed --> companion\n", wIndex + 1); + temp |= PORT_OWNER; + ehci_writel(ehci, temp, status_reg); + } else { + ehci_vdbg(ehci, "port %d reset\n", wIndex + 1); + temp &= ~PORT_PE; + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + ehci->reset_done[wIndex] = jiffies + msecs_to_jiffies(50); + ehci_writel(ehci, temp, status_reg); + if (hsic && (wIndex == 0)) + tegra_usb_phy_bus_reset(tegra->phy); + } + + break; + } + case USB_PORT_FEAT_POWER: + { + if (HCS_PPC(ehci->hcs_params)) + ehci_writel(ehci, temp | PORT_POWER, status_reg); + if (hsic && (wIndex == 0)) + tegra_usb_phy_bus_connect(tegra->phy); + break; + } + } + goto done; + } + spin_unlock_irqrestore(&ehci->lock, flags); /* Handle the hub control events here */ @@ -197,16 +263,16 @@ static void tegra_ehci_restart(struct usb_hcd *hcd) unsigned int temp; struct ehci_hcd *ehci = hcd_to_ehci(hcd); - /* reset the ehci controller */ - ehci->controller_resets_phy = 0; - ehci_reset(ehci); - ehci->controller_resets_phy = 1; - /* Set to Host mode by setting bit 0-1 of USB device mode register */ temp = readl(hcd->regs + TEGRA_USB_USBMODE_REG_OFFSET); writel((temp | TEGRA_USB_USBMODE_HOST), (hcd->regs + TEGRA_USB_USBMODE_REG_OFFSET)); + /* reset the ehci controller */ + ehci->controller_resets_phy = 0; + ehci_reset(ehci); + ehci->controller_resets_phy = 1; + /* setup the frame list and Async q heads */ ehci_writel(ehci, ehci->periodic_dma, &ehci->regs->frame_list); ehci_writel(ehci, (u32)ehci->async->qh_dma, &ehci->regs->async_next); @@ -215,7 +281,14 @@ static void tegra_ehci_restart(struct usb_hcd *hcd) ehci->command |= CMD_RUN; ehci_writel(ehci, ehci->command, &ehci->regs->command); + /* Enable the root Port Power */ + if (HCS_PPC(ehci->hcs_params)) { + temp = ehci_readl(ehci, &ehci->regs->port_status[0]); + ehci_writel(ehci, temp | PORT_POWER, &ehci->regs->port_status[0]); + } + down_write(&ehci_cf_port_reset_rwsem); + hcd->state = HC_STATE_RUNNING; ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); /* flush posted writes */ ehci_readl(ehci, &ehci->regs->command); @@ -228,12 +301,19 @@ static int tegra_usb_suspend(struct usb_hcd *hcd) struct ehci_regs __iomem *hw = tegra->ehci->regs; struct tegra_ehci_context *context = &tegra->context; unsigned long flags; + int hsic = 0; + struct tegra_ulpi_config *config; + + if (tegra->phy->instance == 1) { + config = tegra->phy->config; + hsic = (config->inf_type == TEGRA_USB_UHSIC); + } spin_lock_irqsave(&tegra->ehci->lock, flags); context->port_speed = (readl(&hw->port_status[0]) >> 26) & 0x3; - if (context->port_speed > TEGRA_USB_PHY_PORT_HIGH) { + if ((context->port_speed > TEGRA_USB_PHY_PORT_HIGH) || hsic) { /* If no device connection or invalid speeds, * don't save the context */ context->valid = false; @@ -259,12 +339,19 @@ static int tegra_usb_resume(struct usb_hcd *hcd) { struct tegra_ehci_hcd *tegra = dev_get_drvdata(hcd->self.controller); struct tegra_ehci_context *context = &tegra->context; + struct usb_device *udev = hcd->self.root_hub; struct ehci_regs __iomem *hw = tegra->ehci->regs; unsigned long val; int lp0_resume = 0; + int hsic = 0; + struct tegra_ulpi_config *config; - set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + if (tegra->phy->instance == 1) { + config = tegra->phy->config; + hsic = (config->inf_type == TEGRA_USB_UHSIC); + } tegra_ehci_power_up(ehci_to_hcd(tegra->ehci)); + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); if (!context->valid) { /* Wait for the phy to detect new devices @@ -355,8 +442,19 @@ static int tegra_usb_resume(struct usb_hcd *hcd) restart: if (context->valid) tegra_ehci_phy_restore_end(tegra->phy); + if (hsic) { + val = readl(&hw->port_status[0]); + if (!((val & PORT_POWER) && (val & PORT_PE))) { + tegra_ehci_restart(hcd); + usb_set_device_state(udev, USB_STATE_CONFIGURED); + } + tegra_usb_phy_bus_idle(tegra->phy); + if (!tegra_usb_phy_is_device_connected(tegra->phy)) + schedule_delayed_work(&tegra->work, 50); + } else { + tegra_ehci_restart(hcd); + } - tegra_ehci_restart(hcd); return 0; } @@ -456,7 +554,6 @@ static int tegra_ehci_bus_suspend(struct usb_hcd *hcd) tegra_usb_suspend(hcd); tegra->bus_suspended = 1; } - return error_status; } @@ -476,7 +573,7 @@ static int tegra_ehci_bus_resume(struct usb_hcd *hcd) #endif struct temp_buffer { - void *kmalloc_ptr; + void *kmalloc_ptr; void *old_xfer_buffer; u8 data[0]; }; @@ -492,14 +589,13 @@ static void free_temp_buffer(struct urb *urb) dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; temp = container_of(urb->transfer_buffer, struct temp_buffer, - data); + data); if (dir == DMA_FROM_DEVICE) memcpy(temp->old_xfer_buffer, temp->data, - urb->transfer_buffer_length); + urb->transfer_buffer_length); urb->transfer_buffer = temp->old_xfer_buffer; kfree(temp->kmalloc_ptr); - urb->transfer_flags &= ~URB_ALIGNED_TEMP_BUFFER; } @@ -507,13 +603,12 @@ static int alloc_temp_buffer(struct urb *urb, gfp_t mem_flags) { enum dma_data_direction dir; struct temp_buffer *temp, *kmalloc_ptr; - size_t kmalloc_size; - void *data; + size_t kmalloc_size; if (urb->num_sgs || urb->sg || - urb->transfer_buffer_length == 0 || - !((uintptr_t)urb->transfer_buffer & (TEGRA_USB_DMA_ALIGN - 1))) - return 0; + urb->transfer_buffer_length == 0 || + !((uintptr_t)urb->transfer_buffer & (TEGRA_USB_DMA_ALIGN - 1))) + return 0; dir = usb_urb_dir_in(urb) ? DMA_FROM_DEVICE : DMA_TO_DEVICE; @@ -532,17 +627,17 @@ static int alloc_temp_buffer(struct urb *urb, gfp_t mem_flags) temp->old_xfer_buffer = urb->transfer_buffer; if (dir == DMA_TO_DEVICE) memcpy(temp->data, urb->transfer_buffer, - urb->transfer_buffer_length); + urb->transfer_buffer_length); urb->transfer_buffer = temp->data; - BUILD_BUG_ON(!(URB_ALIGNED_TEMP_BUFFER & URB_DRIVER_PRIVATE)); + BUILD_BUG_ON(!(URB_ALIGNED_TEMP_BUFFER & URB_DRIVER_PRIVATE)); 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) + gfp_t mem_flags) { int ret; @@ -553,7 +648,6 @@ static int tegra_ehci_map_urb_for_dma(struct usb_hcd *hcd, struct urb *urb, ret = usb_hcd_map_urb_for_dma(hcd, urb, mem_flags); if (ret) free_temp_buffer(urb); - return ret; } @@ -563,6 +657,76 @@ static void tegra_ehci_unmap_urb_for_dma(struct usb_hcd *hcd, struct urb *urb) free_temp_buffer(urb); } +static void tegra_hsic_connection_work(struct work_struct *work) +{ + struct tegra_ehci_hcd *tegra = + container_of(work, struct tegra_ehci_hcd, work.work); + if (tegra_usb_phy_is_device_connected(tegra->phy)) { + cancel_delayed_work(&tegra->work); + return; + } + schedule_delayed_work(&tegra->work, jiffies + msecs_to_jiffies(50)); + return; +} + + + +#ifdef CONFIG_USB_EHCI_ONOFF_FEATURE +struct usb_hcd *ehci_handle; /* For ehci on/off sysfs */ +int ehci_tegra_irq; + +static ssize_t show_ehci_power(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "EHCI Power %s\n", (ehci_handle) ? "on" : "off"); +} + +static ssize_t store_ehci_power(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int power_on; + int retval; + struct tegra_ehci_hcd *tegra = dev_get_drvdata(dev); + struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); + + if (sscanf(buf, "%d", &power_on) != 1) + return -EINVAL; + + if (power_on == 0 && ehci_handle != NULL) { + usb_remove_hcd(hcd); + tegra_ehci_power_down(hcd); + ehci_handle = NULL; + } else if (power_on == 1) { + if (ehci_handle) + usb_remove_hcd(hcd); + tegra_ehci_power_up(hcd); + retval = usb_add_hcd(hcd, ehci_tegra_irq, + IRQF_DISABLED | IRQF_SHARED); + if (retval < 0) + printk(KERN_ERR "power_on error\n"); + ehci_handle = hcd; + } + + return count; +} + +static DEVICE_ATTR(ehci_power, 0666, show_ehci_power, store_ehci_power); + +static inline int create_ehci_sys_file(struct ehci_hcd *ehci) +{ + return device_create_file(ehci_to_hcd(ehci)->self.controller, + &dev_attr_ehci_power); +} + +static inline void remove_ehci_sys_file(struct ehci_hcd *ehci) +{ + device_remove_file(ehci_to_hcd(ehci)->self.controller, + &dev_attr_ehci_power); +} +#endif + static const struct hc_driver tegra_ehci_hc_driver = { .description = hcd_name, .product_desc = "Tegra EHCI Host Controller", @@ -664,6 +828,8 @@ static int tegra_ehci_probe(struct platform_device *pdev) config = pdata->phy_config; + INIT_DELAYED_WORK(&tegra->work, tegra_hsic_connection_work); + tegra->phy = tegra_usb_phy_open(instance, hcd->regs, config, TEGRA_USB_PHY_MODE_HOST); if (IS_ERR(tegra->phy)) { @@ -694,6 +860,12 @@ static int tegra_ehci_probe(struct platform_device *pdev) ehci = hcd_to_ehci(hcd); tegra->ehci = ehci; +#ifdef CONFIG_USB_EHCI_ONOFF_FEATURE + if (instance == 1) { + ehci_tegra_irq = irq; + create_ehci_sys_file(ehci); + } +#endif #ifdef CONFIG_USB_OTG_UTILS if (pdata->operating_mode == TEGRA_USB_OTG) { tegra->transceiver = otg_get_transceiver(); @@ -708,6 +880,10 @@ static int tegra_ehci_probe(struct platform_device *pdev) goto fail; } +#ifdef CONFIG_USB_EHCI_ONOFF_FEATURE + if (instance == 1) + ehci_handle = hcd; +#endif return err; fail: @@ -740,7 +916,7 @@ static int tegra_ehci_resume(struct platform_device *pdev) struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); - if (tegra->bus_suspended) + if ((tegra->bus_suspended) && (tegra->power_down_on_bus_suspend)) return 0; return tegra_usb_resume(hcd); @@ -751,7 +927,7 @@ static int tegra_ehci_suspend(struct platform_device *pdev, pm_message_t state) struct tegra_ehci_hcd *tegra = platform_get_drvdata(pdev); struct usb_hcd *hcd = ehci_to_hcd(tegra->ehci); - if (tegra->bus_suspended) + if ((tegra->bus_suspended) && (tegra->power_down_on_bus_suspend)) return 0; if (time_before(jiffies, tegra->ehci->next_statechange)) @@ -776,11 +952,19 @@ static int tegra_ehci_remove(struct platform_device *pdev) } #endif +#ifdef CONFIG_USB_EHCI_ONOFF_FEATURE + if (tegra->phy->instance == 1) { + remove_ehci_sys_file(hcd_to_ehci(hcd)); + ehci_handle = NULL; + } +#endif + /* Turn Off Interrupts */ ehci_writel(tegra->ehci, 0, &tegra->ehci->regs->intr_enable); clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); usb_remove_hcd(hcd); usb_put_hcd(hcd); + cancel_delayed_work(&tegra->work); tegra_usb_phy_power_off(tegra->phy); tegra_usb_phy_close(tegra->phy); iounmap(hcd->regs); |