diff options
author | Albert Chen <R65187@freescale.com> | 2009-10-09 16:44:20 +0800 |
---|---|---|
committer | Albert Chen <R65187@freescale.com> | 2009-10-10 09:44:14 +0800 |
commit | 8635334b08952f242aba2e7cf916a47b117f4169 (patch) | |
tree | dbba7f1ab2bfce6db5bedd8c7a68d9c5dfb04d26 | |
parent | e247e219bdc546d0b130b928570d1af8543e064e (diff) |
ENGR00115875 add USB gadget low power mode feature.
USB will enter low power mode if no vbus power supply.
Only finished this feature in Mx37.
1. When enter low power mode, driver will close usb related clocks
as possible as could.
set usb IP to stop.
Set PHY to low power suspend by setting PHCD bit of portsc.
2. This patch doesn't support low power mode in OTG mode yet.
Signed-off-by: Albert Chen <r65187@freescale.com>
-rw-r--r-- | arch/arm/mach-mx37/usb_dr.c | 19 | ||||
-rw-r--r-- | arch/arm/mach-mx51/usb_dr.c | 17 | ||||
-rw-r--r-- | arch/arm/plat-mxc/include/mach/arc_otg.h | 14 | ||||
-rw-r--r-- | arch/arm/plat-mxc/usb_common.c | 20 | ||||
-rw-r--r-- | drivers/usb/gadget/arcotg_udc.c | 184 | ||||
-rw-r--r-- | drivers/usb/gadget/arcotg_udc.h | 5 | ||||
-rw-r--r-- | include/linux/fsl_devices.h | 10 |
7 files changed, 200 insertions, 69 deletions
diff --git a/arch/arm/mach-mx37/usb_dr.c b/arch/arm/mach-mx37/usb_dr.c index af734624daaf..5c102eb246ac 100644 --- a/arch/arm/mach-mx37/usb_dr.c +++ b/arch/arm/mach-mx37/usb_dr.c @@ -20,6 +20,8 @@ #include "usb.h" static void usbotg_pm_clock(bool on); +static void _wake_up_enable(struct fsl_usb2_platform_data *pdata, bool enable); + /* * platform data structs * - Which one to use is determined by CONFIG options in usb.h @@ -34,6 +36,7 @@ static struct fsl_usb2_platform_data __maybe_unused dr_utmi_config = { .gpio_usb_active = gpio_usbotg_hs_active, .gpio_usb_inactive = gpio_usbotg_hs_inactive, .usb_clock_for_pm = usbotg_pm_clock, + .wake_up_enable = _wake_up_enable, .transceiver = "utmi", }; @@ -133,7 +136,19 @@ static void usbotg_uninit_ext(struct fsl_usb2_platform_data *pdata) usbotg_uninit(pdata); } - +static void _wake_up_enable(struct fsl_usb2_platform_data *pdata, bool enable) +{ + if (get_usb_mode(pdata) == FSL_USB_DR_DEVICE) { + if (enable) { + USBCTRL |= (UCTRL_OWIE | UCTRL_VBUS_WKUP_EN); + USB_PHY_CTR_FUNC |= USB_UTMI_PHYCTRL_CONF2; + } else { + USBCTRL &= ~UCTRL_OWIE; + USBCTRL &= ~UCTRL_VBUS_WKUP_EN; + USB_PHY_CTR_FUNC &= ~USB_UTMI_PHYCTRL_CONF2; + } + } +} static int __init usb_dr_init(void) { @@ -142,7 +157,7 @@ static int __init usb_dr_init(void) dr_register_otg(); dr_register_host(otg_resources, ARRAY_SIZE(otg_resources)); dr_register_udc(); - + device_init_wakeup(&(dr_udc_device.dev), 1); return 0; } diff --git a/arch/arm/mach-mx51/usb_dr.c b/arch/arm/mach-mx51/usb_dr.c index d1d3bdf08e61..b3eb7d645e34 100644 --- a/arch/arm/mach-mx51/usb_dr.c +++ b/arch/arm/mach-mx51/usb_dr.c @@ -21,6 +21,7 @@ static int usbotg_init_ext(struct platform_device *pdev); static void usbotg_uninit_ext(struct fsl_usb2_platform_data *pdata); +static void _wake_up_enable(struct fsl_usb2_platform_data *pdata, bool enable); /* * platform data structs @@ -35,6 +36,7 @@ static struct fsl_usb2_platform_data __maybe_unused dr_utmi_config = { .power_budget = 500, /* 500 mA max power */ .gpio_usb_active = gpio_usbotg_hs_active, .gpio_usb_inactive = gpio_usbotg_hs_inactive, + .wake_up_enable = _wake_up_enable, .transceiver = "utmi", }; @@ -129,6 +131,21 @@ static void usbotg_uninit_ext(struct fsl_usb2_platform_data *pdata) usbotg_uninit(pdata); } +static void _wake_up_enable(struct fsl_usb2_platform_data *pdata, bool enable) +{ + if (get_usb_mode(pdata) == FSL_USB_DR_DEVICE) { + if (enable) { + USBCTRL |= UCTRL_OWIE; + USBCTRL_HOST2 |= UCTRL_H2OVBWK_EN; + USB_PHY_CTR_FUNC |= USB_UTMI_PHYCTRL_CONF2; + } else { + USBCTRL &= ~UCTRL_OWIE; + USBCTRL_HOST2 &= ~UCTRL_H2OVBWK_EN; + USB_PHY_CTR_FUNC &= ~USB_UTMI_PHYCTRL_CONF2; + } + } +} + static int __init usb_dr_init(void) { pr_debug("%s: \n", __func__); diff --git a/arch/arm/plat-mxc/include/mach/arc_otg.h b/arch/arm/plat-mxc/include/mach/arc_otg.h index 2d58f2e6d6fd..467ab1379e62 100644 --- a/arch/arm/plat-mxc/include/mach/arc_otg.h +++ b/arch/arm/plat-mxc/include/mach/arc_otg.h @@ -261,6 +261,8 @@ #define UCTRL_H2PP (1 << 18) /* Power Polarity for uh2 */ #define UCTRL_H2PM (1 << 16) /* HOST2 power mask */ #endif +#define UCTRL_H2OVBWK_EN (1 << 6) /* OTG VBUS Wakeup Enable */ +#define UCTRL_H2OIDWK_EN (1 << 5) /* OTG ID Wakeup Enable */ #define UCTRL_H1WIR (1 << 15) /* HOST1 wakeup intr request received */ #define UCTRL_H1SIC_MASK (3 << 13) /* HOST1 Serial Interface Config: */ @@ -272,6 +274,13 @@ #define UCTRL_H2LOCKD (1 << 12) /* HOST2 lock disable */ #define UCTRL_H1UIE (1 << 12) /* Host1 ULPI interrupt enable */ +#if defined(CONFIG_ARCH_MX37) +/* VBUS wakeup enable, UTMI only */ +#define UCTRL_VBUS_WKUP_EN (1 << 12) +#elif defined(CONFIG_ARCH_MX25) || defined(CONFIG_ARCH_MX35) +#define UCTRL_VBUS_WKUP_EN (1 << 15) +#endif + #define UCTRL_PP (1 << 11) /* power polarity bit */ #define UCTRL_H1WIE (1 << 11) /* HOST1 wakeup intr enable */ #define UCTRL_H1BPVAL_RXDP (1 << 10) /* HOST1 RxDp status in bypass mode */ @@ -309,6 +318,9 @@ /* 0=low : Operate as A-device */ /* USB_PHY_CTRL_FUNC */ +/* PHY control0 Register Bit Masks */ +#define USB_UTMI_PHYCTRL_CONF2 (1 << 26) + #define USB_UTMI_PHYCTRL_UTMI_ENABLE (1 << 24) #define USB_UTMI_PHYCTRL_CHGRDETEN (1 << 24) /* Enable Charger Detector */ #define USB_UTMI_PHYCTRL_CHGRDETON (1 << 23) /* Charger Detector Power On Control */ @@ -340,4 +352,6 @@ #define ULPIVW_WDATA_SHIFT 0 #define HCSPARAMS_PPC (0x1<<4) /* Port Power Control */ + +extern enum fsl_usb2_modes get_usb_mode(struct fsl_usb2_platform_data *pdata); #endif diff --git a/arch/arm/plat-mxc/usb_common.c b/arch/arm/plat-mxc/usb_common.c index bebd692b50bd..389efb7e128d 100644 --- a/arch/arm/plat-mxc/usb_common.c +++ b/arch/arm/plat-mxc/usb_common.c @@ -48,6 +48,26 @@ #define MXC_NUMBER_USB_TRANSCEIVER 6 struct fsl_xcvr_ops *g_xc_ops[MXC_NUMBER_USB_TRANSCEIVER] = { NULL }; +enum fsl_usb2_modes get_usb_mode(struct fsl_usb2_platform_data *pdata) +{ + enum fsl_usb2_modes mode; + mode = FSL_USB_UNKNOWN; + + if (!strcmp("DR", pdata->name)) { + if ((UOG_USBMODE & 0x3) == 0x2) + mode = FSL_USB_DR_DEVICE; + else if ((UOG_USBMODE & 0x3) == 0x3) + mode = FSL_USB_DR_HOST; + } else if (!strcmp("Host 1", pdata->name)) + mode = FSL_USB_MPH_HOST1; + else if (!strcmp("Host 2", pdata->name)) + mode = FSL_USB_MPH_HOST2; + + if (mode == FSL_USB_UNKNOWN) + printk(KERN_ERR "unknow usb mode,name is %s\n", pdata->name); + return mode; +} + static struct clk *usb_clk; static struct clk *usb_ahb_clk; diff --git a/drivers/usb/gadget/arcotg_udc.c b/drivers/usb/gadget/arcotg_udc.c index 7c7af5dfcec0..4ae2475a319f 100644 --- a/drivers/usb/gadget/arcotg_udc.c +++ b/drivers/usb/gadget/arcotg_udc.c @@ -103,6 +103,18 @@ extern struct resource *otg_get_resources(void); extern void fsl_platform_set_test_mode(struct fsl_usb2_platform_data *pdata, enum usb_test_mode mode); +static inline void +dr_wake_up_enable(struct fsl_udc *udc, bool enable) +{ + struct fsl_usb2_platform_data *pdata; + pdata = udc->pdata; + + if (device_can_wakeup(udc_controller->gadget.dev.parent)) { + if (pdata->wake_up_enable) + pdata->wake_up_enable(pdata, enable); + } +} + #ifdef CONFIG_PPC32 #define fsl_readl(addr) in_le32((addr)) #define fsl_writel(addr, val32) out_le32((val32), (addr)) @@ -260,9 +272,8 @@ static void dr_phy_low_power_mode(struct fsl_udc *udc, bool enable) { u32 temp; - if (!device_may_wakeup(&(udc->pdata->pdev->dev))) + if (!device_can_wakeup(udc_controller->gadget.dev.parent)) return; - temp = fsl_readl(&dr_regs->portsc1); if ((enable) && !(temp & PORTSCX_PHY_LOW_POWER_SPD)) { temp |= PORTSCX_PHY_LOW_POWER_SPD; @@ -279,44 +290,7 @@ static void dr_phy_low_power_mode(struct fsl_udc *udc, bool enable) } } -static void dr_wake_up_enable(struct fsl_udc *udc, bool enable) -{ - u32 temp; - - temp = fsl_readl(&dr_regs->usbctrl); - - if (!enable) { - temp &= ~USB_CTRL_OTG_WUIE; - fsl_writel(temp, &dr_regs->usbctrl); -#if CONFIG_ARCH_MX51 - /* OTG vbus Wakeup disable */ - temp = fsl_readl(&dr_regs->uh2ctrl); - temp &= ~USB_UH2_OVBWK_EN; - fsl_writel(temp, &dr_regs->uh2ctrl); - - /* disable conf2 */ - temp = fsl_readl(&dr_regs->phyctrl0); - temp &= ~PHY_CTRL0_CONF2; - fsl_writel(temp, &dr_regs->phyctrl0); -#endif - } else if (device_may_wakeup(&(udc->pdata->pdev->dev))) { - temp |= USB_CTRL_OTG_WUIE; - fsl_writel(temp, &dr_regs->usbctrl); - -#if CONFIG_ARCH_MX51 - /* OTG vbus wakeup enable */ - temp = fsl_readl(&dr_regs->uh2ctrl); - temp |= USB_UH2_OVBWK_EN; - fsl_writel(temp, &dr_regs->uh2ctrl); - - /* enable conf2 */ - temp = fsl_readl(&dr_regs->phyctrl0); - temp |= PHY_CTRL0_CONF2; - fsl_writel(temp, &dr_regs->phyctrl0); -#endif - } -} static int dr_controller_setup(struct fsl_udc *udc) { @@ -439,14 +413,41 @@ static void dr_controller_run(struct fsl_udc *udc) fsl_writel(temp, &dr_regs->usbintr); - /* Clear stopped bit */ - udc->stopped = 0; + /* If PHY clock is disabled, enable it */ + if (udc_controller->pdata->usb_clock_for_pm) + udc_controller->pdata->usb_clock_for_pm(1); - /* Set controller to Run */ - if (udc->driver) { + if (device_can_wakeup(udc_controller->gadget.dev.parent)) { + /* enable BSV irq */ + temp = fsl_readl(&dr_regs->otgsc); + temp |= OTGSC_B_SESSION_VALID_IRQ_EN; + fsl_writel(temp, &dr_regs->otgsc); + } + + /* If vbus not on and used low power mode */ + if (!(fsl_readl(&dr_regs->otgsc) & OTGSC_B_SESSION_VALID) + && device_can_wakeup(udc_controller->gadget.dev.parent)) { + /* enable wake up */ + dr_wake_up_enable(udc, true); + /* Set stopped before low power mode */ + udc->stopped = 1; + /* close PHY clock */ + dr_phy_low_power_mode(udc, true); + printk(KERN_INFO "udc enter low power mode \n"); + } else { + /* + add some delay for USB timing issue. USB may be + recognize as FS device + during USB gadget remote wake up function + */ + mdelay(100); + /* Clear stopped bit */ + udc->stopped = 0; + /* Set controller to Run */ temp = fsl_readl(&dr_regs->usbcmd); temp |= USB_CMD_RUN_STOP; fsl_writel(temp, &dr_regs->usbcmd); + printk(KERN_INFO "udc run \n"); } return; @@ -472,6 +473,13 @@ static void dr_controller_stop(struct fsl_udc *udc) /* disable all INTR */ fsl_writel(0, &dr_regs->usbintr); + /* disable wake up */ + dr_wake_up_enable(udc, false); + /* disable BSV irq */ + tmp = fsl_readl(&dr_regs->otgsc); + tmp &= ~OTGSC_B_SESSION_VALID_IRQ_EN; + fsl_writel(tmp, &dr_regs->otgsc); + /* Set stopped bit for isr */ udc->stopped = 1; @@ -1932,7 +1940,10 @@ static void wake_up_irq(struct fsl_udc *udc) pr_debug("%s\n", __func__); /* disable wake up irq */ - dr_wake_up_enable(udc, false); + dr_wake_up_enable(udc_controller, false); + if (udc_controller->pdata->usb_clock_for_pm) + udc_controller->pdata->usb_clock_for_pm(true); + udc->stopped = 0; } static void bus_resume(struct fsl_udc *udc) @@ -2014,41 +2025,64 @@ static void reset_irq(struct fsl_udc *udc) } } -/* - * USB device controller interrupt handler - */ -static irqreturn_t fsl_udc_irq(int irq, void *_udc) +/* if wakup udc, return true; else return false*/ +bool try_wake_up_udc(struct fsl_udc *udc) { - struct fsl_udc *udc = _udc; u32 irq_src; - irqreturn_t status = IRQ_NONE; - unsigned long flags; /* when udc is stopped, only handle wake up irq */ if (udc->stopped) { - if (!device_may_wakeup(&(udc->pdata->pdev->dev))) - return IRQ_NONE; - - spin_lock_irqsave(&udc->lock, flags); + if (!device_can_wakeup(&(udc->pdata->pdev->dev))) + return false; /* check to see if wake up irq */ irq_src = fsl_readl(&dr_regs->usbctrl); if (irq_src & USB_CTRL_OTG_WUIR) { wake_up_irq(udc); - irq_src = fsl_readl(&dr_regs->usbsts) & - fsl_readl(&dr_regs->usbintr); - spin_unlock_irqrestore(&udc->lock, flags); - if (irq_src) - /* Some udc irq to be handled */ - udc->stopped = 0; - else - return IRQ_HANDLED; + } + } + + if (!device_can_wakeup(udc_controller->gadget.dev.parent)) + return true; + + /* check if Vbus change irq */ + irq_src = fsl_readl(&dr_regs->otgsc); + if (irq_src & OTGSC_B_SESSION_VALID_IRQ_STS) { + u32 tmp; + fsl_writel(irq_src, &dr_regs->otgsc); + tmp = fsl_readl(&dr_regs->usbcmd); + /* check BSV bit to see if fall or rise */ + if (irq_src & OTGSC_B_SESSION_VALID) { + udc->stopped = 0; + fsl_writel(tmp | USB_CMD_RUN_STOP, &dr_regs->usbcmd); + printk(KERN_INFO "udc out low power mode\n"); } else { - /* If udc is stopped and irq is not wake up */ - spin_unlock_irqrestore(&udc->lock, flags); - return IRQ_NONE; + printk(KERN_INFO "udc enter low power mode \n"); + fsl_writel(tmp & ~USB_CMD_RUN_STOP, &dr_regs->usbcmd); + /* enable wake up */ + dr_wake_up_enable(udc, true); + udc->stopped = 1; + /* close USB PHY clock */ + dr_phy_low_power_mode(udc, true); + return false; } } + return true; +} + +/* + * USB device controller interrupt handler + */ +static irqreturn_t fsl_udc_irq(int irq, void *_udc) +{ + struct fsl_udc *udc = _udc; + u32 irq_src; + irqreturn_t status = IRQ_NONE; + unsigned long flags; + + if (try_wake_up_udc(udc) == false) + return IRQ_NONE; + spin_lock_irqsave(&udc->lock, flags); irq_src = fsl_readl(&dr_regs->usbsts) & fsl_readl(&dr_regs->usbintr); /* Clear notification bits */ @@ -2141,6 +2175,7 @@ int usb_gadget_register_driver(struct usb_gadget_driver *driver) udc_controller->gadget.dev.driver = &driver->driver; spin_unlock_irqrestore(&udc_controller->lock, flags); + dr_phy_low_power_mode(udc_controller, false); /* bind udc driver to gadget driver */ retval = driver->bind(&udc_controller->gadget); if (retval) { @@ -2206,6 +2241,8 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) /* stop DR, disable intr */ dr_controller_stop(udc_controller); + /* open phy clock for following operation */ + dr_phy_low_power_mode(udc_controller, false); /* in fact, no needed */ udc_controller->usb_state = USB_STATE_ATTACHED; @@ -2228,6 +2265,9 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) udc_controller->gadget.dev.driver = 0; udc_controller->driver = 0; + dr_wake_up_enable(udc_controller, false); + dr_phy_low_power_mode(udc_controller, true); + printk(KERN_INFO "unregistered gadget driver '%s'\r\n", driver->driver.name); return 0; @@ -2758,8 +2798,11 @@ static int __init fsl_udc_probe(struct platform_device *pdev) if (ret < 0) goto err3; - if (udc_controller->transceiver) + if (udc_controller->transceiver) { udc_controller->gadget.is_otg = 1; + /* now didn't support lpm in OTG mode*/ + device_set_wakeup_capable(&pdev->dev, 0); + } /* setup QH and epctrl for ep0 */ ep0_setup(udc_controller); @@ -2802,6 +2845,11 @@ static int __init fsl_udc_probe(struct platform_device *pdev) #ifdef POSTPONE_FREE_LAST_DTD last_free_td = NULL; #endif + + dr_wake_up_enable(udc_controller, false); + udc_controller->stopped = 1; + dr_phy_low_power_mode(udc_controller, true); + create_proc_file(); return 0; @@ -2837,6 +2885,8 @@ static int __exit fsl_udc_remove(struct platform_device *pdev) if (!udc_controller) return -ENODEV; udc_controller->done = &done; + /* open USB PHY clock */ + dr_phy_low_power_mode(udc_controller, false); /* DR has been stopped in usb_gadget_unregister_driver() */ remove_proc_file(); diff --git a/drivers/usb/gadget/arcotg_udc.h b/drivers/usb/gadget/arcotg_udc.h index e7cca079e184..3d13b9af6155 100644 --- a/drivers/usb/gadget/arcotg_udc.h +++ b/drivers/usb/gadget/arcotg_udc.h @@ -262,6 +262,11 @@ struct usb_sys_interface { #define PORTSCX_PORT_SPEED_UNDEF (0x0C000000) #define PORTSCX_SPEED_BIT_POS (26) +/* OTGSC Register Bit Masks */ +#define OTGSC_B_SESSION_VALID_IRQ_EN (1 << 27) +#define OTGSC_B_SESSION_VALID_IRQ_STS (1 << 19) +#define OTGSC_B_SESSION_VALID (1 << 11) + /* bit 28 is parallel transceiver width for UTMI interface */ #define PORTSCX_PTW (0x10000000) #define PORTSCX_PTW_8BIT (0x00000000) diff --git a/include/linux/fsl_devices.h b/include/linux/fsl_devices.h index ba1359cc75e5..25cc80e0f1cb 100644 --- a/include/linux/fsl_devices.h +++ b/include/linux/fsl_devices.h @@ -92,6 +92,15 @@ enum fsl_usb2_operating_modes { FSL_USB2_DR_OTG, }; +/* this used for usb port type */ +enum fsl_usb2_modes { + FSL_USB_DR_HOST, + FSL_USB_DR_DEVICE, + FSL_USB_MPH_HOST1, + FSL_USB_MPH_HOST2, + FSL_USB_UNKNOWN, /* unkonwn status */ +}; + enum fsl_usb2_phy_modes { FSL_USB2_PHY_NONE, FSL_USB2_PHY_ULPI, @@ -122,6 +131,7 @@ struct fsl_usb2_platform_data { void (*usb_clock_for_pm) (bool); void (*platform_suspend)(struct fsl_usb2_platform_data *); void (*platform_resume)(struct fsl_usb2_platform_data *); + void (*wake_up_enable)(struct fsl_usb2_platform_data *pdata, bool on); unsigned big_endian_mmio : 1; unsigned big_endian_desc : 1; unsigned es : 1; /* need USBMODE:ES */ |