diff options
author | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
---|---|---|
committer | codeworkx <daniel.hillenbrand@codeworkx.de> | 2012-06-02 13:09:29 +0200 |
commit | c6da2cfeb05178a11c6d062a06f8078150ee492f (patch) | |
tree | f3b4021d252c52d6463a9b3c1bb7245e399b009c /drivers/usb/host/ehci-s5p.c | |
parent | c6d7c4dbff353eac7919342ae6b3299a378160a6 (diff) | |
download | kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.zip kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.gz kernel_samsung_smdk4412-c6da2cfeb05178a11c6d062a06f8078150ee492f.tar.bz2 |
samsung update 1
Diffstat (limited to 'drivers/usb/host/ehci-s5p.c')
-rw-r--r-- | drivers/usb/host/ehci-s5p.c | 487 |
1 files changed, 482 insertions, 5 deletions
diff --git a/drivers/usb/host/ehci-s5p.c b/drivers/usb/host/ehci-s5p.c index 491a209..8cb7ae2 100644 --- a/drivers/usb/host/ehci-s5p.c +++ b/drivers/usb/host/ehci-s5p.c @@ -14,17 +14,324 @@ #include <linux/clk.h> #include <linux/platform_device.h> -#include <mach/regs-pmu.h> +#include <linux/pm_runtime.h> + #include <plat/cpu.h> #include <plat/ehci.h> #include <plat/usb-phy.h> +#include <mach/regs-pmu.h> +#include <mach/regs-usb-host.h> +#include <mach/board_rev.h> + struct s5p_ehci_hcd { struct device *dev; struct usb_hcd *hcd; struct clk *clk; + int power_on; }; +#ifdef CONFIG_USB_EXYNOS_SWITCH +int s5p_ehci_port_power_off(struct platform_device *pdev) +{ + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + (void) ehci_hub_control(hcd, + ClearPortFeature, + USB_PORT_FEAT_POWER, + 1, NULL, 0); + /* Flush those writes */ + ehci_readl(ehci, &ehci->regs->command); + return 0; +} +EXPORT_SYMBOL_GPL(s5p_ehci_port_power_off); + +int s5p_ehci_port_power_on(struct platform_device *pdev) +{ + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + (void) ehci_hub_control(hcd, + SetPortFeature, + USB_PORT_FEAT_POWER, + 1, NULL, 0); + /* Flush those writes */ + ehci_readl(ehci, &ehci->regs->command); + return 0; +} +EXPORT_SYMBOL_GPL(s5p_ehci_port_power_on); +#endif + +static int s5p_ehci_configurate(struct usb_hcd *hcd) +{ + int delay_count = 0; + + /* This is for waiting phy before ehci configuration */ + do { + if (readl(hcd->regs)) + break; + udelay(1); + ++delay_count; + } while (delay_count < 200); + if (delay_count) + dev_info(hcd->self.controller, "phy delay count = %d\n", + delay_count); + + /* DMA burst Enable, set utmi suspend_on_n */ + writel(readl(INSNREG00(hcd->regs)) | ENA_DMA_INCR | OHCI_SUSP_LGCY, + INSNREG00(hcd->regs)); + return 0; +} + +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) ||\ + defined(CONFIG_CDMA_MODEM_MDM6600) +#define CP_PORT 2 /* HSIC0 in S5PC210 */ +#define RETRY_CNT_LIMIT 30 /* Max 300ms wait for cp resume*/ + +int s5p_ehci_port_control(struct platform_device *pdev, int port, int enable) +{ + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + (void) ehci_hub_control(hcd, + enable ? SetPortFeature : ClearPortFeature, + USB_PORT_FEAT_POWER, + port, NULL, 0); + /* Flush those writes */ + ehci_readl(ehci, &ehci->regs->command); + return 0; +} + +static void s5p_wait_for_cp_resume(struct platform_device *pdev, + struct usb_hcd *hcd) +{ + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + u32 __iomem *portsc ; + u32 val32, retry_cnt = 0; + + portsc = &ehci->regs->port_status[CP_PORT-1]; + + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_ON); + + do { + msleep(10); + val32 = ehci_readl(ehci, portsc); + } while (++retry_cnt < RETRY_CNT_LIMIT && !(val32 & PORT_CONNECT)); + + if (retry_cnt >= RETRY_CNT_LIMIT) + dev_info(&pdev->dev, "%s: retry_cnt = %d, portsc = 0x%x\n", + __func__, retry_cnt, val32); + +#if defined(CONFIG_UMTS_MODEM_XMM6262) + if (pdata->get_cp_active_state && !pdata->get_cp_active_state()) { + s5p_ehci_port_control(pdev, CP_PORT, 0); + pr_err("mif: force port%d off by cp reset\n", CP_PORT); + } +#endif +} +#endif + +static void s5p_ehci_phy_init(struct platform_device *pdev) +{ + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + u32 delay_count = 0; + + if (pdata->phy_init) { + pdata->phy_init(pdev, S5P_USB_PHY_HOST); + + while (!readl(hcd->regs) && delay_count < 200) { + delay_count++; + udelay(1); + } + if (delay_count) + dev_info(&pdev->dev, "waiting time = %d\n", + delay_count); + s5p_ehci_configurate(hcd); + dev_dbg(&pdev->dev, "%s : 0x%x\n", __func__, + readl(INSNREG00(hcd->regs))); + } + +} + +#ifdef CONFIG_PM +static int s5p_ehci_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + unsigned long flags; + int rc = 0; + + if (time_before(jiffies, ehci->next_statechange)) + msleep(10); + + /* Root hub was already suspended. Disable irq emission and + * mark HW unaccessible, bail out if RH has been resumed. Use + * the spinlock to properly synchronize with possible pending + * RH suspend or resume activity. + * + * This is still racy as hcd->state is manipulated outside of + * any locks =P But that will be a different fix. + */ + + spin_lock_irqsave(&ehci->lock, flags); + if (hcd->state != HC_STATE_SUSPENDED && hcd->state != HC_STATE_HALT) { + spin_unlock_irqrestore(&ehci->lock, flags); + return -EINVAL; + } + ehci_writel(ehci, 0, &ehci->regs->intr_enable); + (void)ehci_readl(ehci, &ehci->regs->intr_enable); + + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + spin_unlock_irqrestore(&ehci->lock, flags); + + if (pdata && pdata->phy_exit) + pdata->phy_exit(pdev, S5P_USB_PHY_HOST); +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_OFF); +#endif + + clk_disable(s5p_ehci->clk); + + return rc; +} + +static int s5p_ehci_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + clk_enable(s5p_ehci->clk); + pm_runtime_resume(&pdev->dev); + + s5p_ehci_phy_init(pdev); + + /* if EHCI was off, hcd was removed */ + if (!s5p_ehci->power_on) { + dev_info(dev, "Nothing to do for the device (power off)\n"); + return 0; + } + + if (time_before(jiffies, ehci->next_statechange)) + msleep(10); + + /* Mark hardware accessible again as we are out of D3 state by now */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + if (ehci_readl(ehci, &ehci->regs->configured_flag) == FLAG_CF) { + int mask = INTR_MASK; + + if (!hcd->self.root_hub->do_remote_wakeup) + mask &= ~STS_PCD; + ehci_writel(ehci, mask, &ehci->regs->intr_enable); + ehci_readl(ehci, &ehci->regs->intr_enable); + return 0; + } + + ehci_dbg(ehci, "lost power, restarting\n"); + usb_root_hub_lost_power(hcd->self.root_hub); + + (void) ehci_halt(ehci); + (void) ehci_reset(ehci); + + /* emptying the schedule aborts any urbs */ + spin_lock_irq(&ehci->lock); + if (ehci->reclaim) + end_unlink_async(ehci); + ehci_work(ehci); + spin_unlock_irq(&ehci->lock); + + ehci_writel(ehci, ehci->command, &ehci->regs->command); + ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); + ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ + + /* here we "know" root ports should always stay powered */ + ehci_port_power(ehci, 1); + + hcd->state = HC_STATE_SUSPENDED; +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + s5p_wait_for_cp_resume(pdev, hcd); +#endif + return 0; +} + +#else +#define s5p_ehci_suspend NULL +#define s5p_ehci_resume NULL +#endif + +#ifdef CONFIG_USB_SUSPEND +static int s5p_ehci_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + + if (pdata && pdata->phy_suspend) + pdata->phy_suspend(pdev, S5P_USB_PHY_HOST); + + return 0; +} + +static int s5p_ehci_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + int rc = 0; + + if (dev->power.is_suspended) + return 0; + + /* platform device isn't suspended */ + if (pdata && pdata->phy_resume) + rc = pdata->phy_resume(pdev, S5P_USB_PHY_HOST); + + if (rc) { + s5p_ehci_configurate(hcd); + + /* emptying the schedule aborts any urbs */ + spin_lock_irq(&ehci->lock); + if (ehci->reclaim) + end_unlink_async(ehci); + ehci_work(ehci); + spin_unlock_irq(&ehci->lock); + + usb_root_hub_lost_power(hcd->self.root_hub); + + ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); + ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); + (void)ehci_readl(ehci, &ehci->regs->intr_enable); + + /* here we "know" root ports should always stay powered */ + ehci_port_power(ehci, 1); + + hcd->state = HC_STATE_SUSPENDED; +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + s5p_wait_for_cp_resume(pdev, hcd); +#endif + } + + return 0; +} +#else +#define s5p_ehci_runtime_suspend NULL +#define s5p_ehci_runtime_resume NULL +#endif + static const struct hc_driver s5p_ehci_hc_driver = { .description = hcd_name, .product_desc = "S5P EHCI Host Controller", @@ -56,6 +363,140 @@ static const struct hc_driver s5p_ehci_hc_driver = { .clear_tt_buffer_complete = ehci_clear_tt_buffer_complete, }; +static ssize_t show_ehci_power(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + + return sprintf(buf, "EHCI Power %s\n", (s5p_ehci->power_on) ? "on" : "off"); +} + +static ssize_t store_ehci_power(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); + struct usb_hcd *hcd = s5p_ehci->hcd; + int power_on; + int irq; + int retval; + + if (sscanf(buf, "%d", &power_on) != 1) + return -EINVAL; + + device_lock(dev); + + if (!power_on && s5p_ehci->power_on) { + printk(KERN_DEBUG "%s: EHCI turns off\n", __func__); + pm_runtime_forbid(dev); + s5p_ehci->power_on = 0; + usb_remove_hcd(hcd); + + if (pdata && pdata->phy_exit) + pdata->phy_exit(pdev, S5P_USB_PHY_HOST); + +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + /*HSIC IPC control the ACTIVE_STATE*/ + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_OFF); +#endif + } else if (power_on) { + printk(KERN_DEBUG "%s: EHCI turns on\n", __func__); + if (s5p_ehci->power_on) { + pm_runtime_forbid(dev); + usb_remove_hcd(hcd); +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + /*HSIC IPC control the ACTIVE_STATE*/ + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_OFF); +#endif + } else + s5p_ehci_phy_init(pdev); + + irq = platform_get_irq(pdev, 0); + retval = usb_add_hcd(hcd, irq, + IRQF_DISABLED | IRQF_SHARED); + if (retval < 0) { + dev_err(dev, "Power On Fail\n"); + goto exit; + } + + s5p_ehci->power_on = 1; +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + /* Sometimes XMM6262 send remote wakeup when hub enter suspend + * So, set the hub waiting 500ms autosuspend delay*/ + if (hcd->self.root_hub) + pm_runtime_set_autosuspend_delay( + &hcd->self.root_hub->dev, + msecs_to_jiffies(500)); + + /*HSIC IPC control the ACTIVE_STATE*/ + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_ON); +#endif + pm_runtime_allow(dev); + } +exit: + device_unlock(dev); + return count; +} +static DEVICE_ATTR(ehci_power, 0664, show_ehci_power, store_ehci_power); + +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) +static ssize_t store_port_power(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct s5p_ehci_platdata *pdata = pdev->dev.platform_data; + + int power_on, port; + int err; + + err = sscanf(buf, "%d %d", &power_on, &port); + if (err < 2 || port < 0 || port > 3 || power_on < 0 || power_on > 1) { + pr_err("port power fail: port_power 1 2(port 2 enable 1)\n"); + return count; + } + + pr_debug("%s: Port:%d power: %d\n", __func__, port, power_on); + device_lock(dev); + s5p_ehci_port_control(pdev, port, power_on); + + /*HSIC IPC control the ACTIVE_STATE*/ + if (pdata && pdata->noti_host_states && port == CP_PORT) + pdata->noti_host_states(pdev, power_on ? S5P_HOST_ON : + S5P_HOST_OFF); + device_unlock(dev); + return count; +} +static DEVICE_ATTR(port_power, 0664, NULL, store_port_power); +#endif + +static inline int create_ehci_sys_file(struct ehci_hcd *ehci) +{ +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + BUG_ON(device_create_file(ehci_to_hcd(ehci)->self.controller, + &dev_attr_port_power)); +#endif + 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); +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + device_remove_file(ehci_to_hcd(ehci)->self.controller, + &dev_attr_port_power); +#endif +} + static int __devinit s5p_ehci_probe(struct platform_device *pdev) { struct s5p_ehci_platdata *pdata; @@ -75,7 +516,6 @@ static int __devinit s5p_ehci_probe(struct platform_device *pdev) s5p_ehci = kzalloc(sizeof(struct s5p_ehci_hcd), GFP_KERNEL); if (!s5p_ehci) return -ENOMEM; - s5p_ehci->dev = &pdev->dev; hcd = usb_create_hcd(&s5p_ehci_hc_driver, &pdev->dev, @@ -122,8 +562,9 @@ static int __devinit s5p_ehci_probe(struct platform_device *pdev) goto fail; } - if (pdata->phy_init) - pdata->phy_init(pdev, S5P_USB_PHY_HOST); + platform_set_drvdata(pdev, s5p_ehci); + + s5p_ehci_phy_init(pdev); ehci = hcd_to_ehci(hcd); ehci->caps = hcd->regs; @@ -142,7 +583,20 @@ static int __devinit s5p_ehci_probe(struct platform_device *pdev) goto fail; } - platform_set_drvdata(pdev, s5p_ehci); + create_ehci_sys_file(ehci); + s5p_ehci->power_on = 1; + +#ifdef CONFIG_USB_SUSPEND + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); +#endif +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + /* for cp enumeration */ + pm_runtime_forbid(&pdev->dev); + /*HSIC IPC control the ACTIVE_STATE*/ + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_ON); +#endif return 0; @@ -165,8 +619,18 @@ static int __devexit s5p_ehci_remove(struct platform_device *pdev) struct s5p_ehci_hcd *s5p_ehci = platform_get_drvdata(pdev); struct usb_hcd *hcd = s5p_ehci->hcd; +#ifdef CONFIG_USB_SUSPEND + pm_runtime_disable(&pdev->dev); +#endif + s5p_ehci->power_on = 0; + remove_ehci_sys_file(hcd_to_ehci(hcd)); usb_remove_hcd(hcd); +#if defined(CONFIG_LINK_DEVICE_HSIC) || defined(CONFIG_LINK_DEVICE_USB) + /*HSIC IPC control the ACTIVE_STATE*/ + if (pdata && pdata->noti_host_states) + pdata->noti_host_states(pdev, S5P_HOST_OFF); +#endif if (pdata && pdata->phy_exit) pdata->phy_exit(pdev, S5P_USB_PHY_HOST); @@ -190,6 +654,18 @@ static void s5p_ehci_shutdown(struct platform_device *pdev) hcd->driver->shutdown(hcd); } +static const struct dev_pm_ops s5p_ehci_pm_ops = { + .suspend = s5p_ehci_suspend, + .resume = s5p_ehci_resume, +#ifdef CONFIG_HIBERNATION + .freeze = s5p_ehci_suspend, + .thaw = s5p_ehci_resume, + .restore = s5p_ehci_resume, +#endif + .runtime_suspend = s5p_ehci_runtime_suspend, + .runtime_resume = s5p_ehci_runtime_resume, +}; + static struct platform_driver s5p_ehci_driver = { .probe = s5p_ehci_probe, .remove = __devexit_p(s5p_ehci_remove), @@ -197,6 +673,7 @@ static struct platform_driver s5p_ehci_driver = { .driver = { .name = "s5p-ehci", .owner = THIS_MODULE, + .pm = &s5p_ehci_pm_ops, } }; |