From 0eadcc09203349b11ca477ec367079b23d32ab91 Mon Sep 17 00:00:00 2001 From: Tatyana Brokhman Date: Mon, 1 Nov 2010 18:18:24 +0200 Subject: usb: USB3.0 ch11 definitions Adding hub SuperSpeed usb definitions as defined by ch10 of the USB3.0 spec. Signed-off-by: Tatyana Brokhman Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/ch11.h | 47 +++++++++++++++++++++++++++++++++++++++++++++++ include/linux/usb/hcd.h | 4 ++++ 2 files changed, 51 insertions(+) (limited to 'include/linux/usb') diff --git a/include/linux/usb/ch11.h b/include/linux/usb/ch11.h index 119194c85d10..10ec0699bea4 100644 --- a/include/linux/usb/ch11.h +++ b/include/linux/usb/ch11.h @@ -27,6 +27,13 @@ #define HUB_GET_TT_STATE 10 #define HUB_STOP_TT 11 +/* + * Hub class additional requests defined by USB 3.0 spec + * See USB 3.0 spec Table 10-6 + */ +#define HUB_SET_DEPTH 12 +#define HUB_GET_PORT_ERR_COUNT 13 + /* * Hub Class feature numbers * See USB 2.0 spec Table 11-17 @@ -55,6 +62,20 @@ #define USB_PORT_FEAT_INDICATOR 22 #define USB_PORT_FEAT_C_PORT_L1 23 +/* + * Port feature selectors added by USB 3.0 spec. + * See USB 3.0 spec Table 10-7 + */ +#define USB_PORT_FEAT_LINK_STATE 5 +#define USB_PORT_FEAT_U1_TIMEOUT 23 +#define USB_PORT_FEAT_U2_TIMEOUT 24 +#define USB_PORT_FEAT_C_LINK_STATE 25 +#define USB_PORT_FEAT_C_CONFIG_ERR 26 +#define USB_PORT_FEAT_REMOTE_WAKE_MASK 27 +#define USB_PORT_FEAT_BH_PORT_RESET 28 +#define USB_PORT_FEAT_C_BH_PORT_RESET 29 +#define USB_PORT_FEAT_FORCE_LINKPM_ACCEPT 30 + /* * Hub Status and Hub Change results * See USB 2.0 spec Table 11-19 and Table 11-20 @@ -83,6 +104,32 @@ struct usb_port_status { /* bits 13 to 15 are reserved */ #define USB_PORT_STAT_SUPER_SPEED 0x8000 /* Linux-internal */ +/* + * Additions to wPortStatus bit field from USB 3.0 + * See USB 3.0 spec Table 10-10 + */ +#define USB_PORT_STAT_LINK_STATE 0x01e0 +#define USB_SS_PORT_STAT_POWER 0x0200 +#define USB_PORT_STAT_SPEED_5GBPS 0x0000 +/* Valid only if port is enabled */ + +/* + * Definitions for PORT_LINK_STATE values + * (bits 5-8) in wPortStatus + */ +#define USB_SS_PORT_LS_U0 0x0000 +#define USB_SS_PORT_LS_U1 0x0020 +#define USB_SS_PORT_LS_U2 0x0040 +#define USB_SS_PORT_LS_U3 0x0060 +#define USB_SS_PORT_LS_SS_DISABLED 0x0080 +#define USB_SS_PORT_LS_RX_DETECT 0x00a0 +#define USB_SS_PORT_LS_SS_INACTIVE 0x00c0 +#define USB_SS_PORT_LS_POLLING 0x00e0 +#define USB_SS_PORT_LS_RECOVERY 0x0100 +#define USB_SS_PORT_LS_HOT_RESET 0x0120 +#define USB_SS_PORT_LS_COMP_MOD 0x0140 +#define USB_SS_PORT_LS_LOOPBACK 0x0160 + /* * wPortChange bit field * See USB 2.0 spec Table 11-22 diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index 0b6e751ea0b1..dd6ee49a0844 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -471,6 +471,10 @@ extern void usb_ep0_reinit(struct usb_device *); /*-------------------------------------------------------------------------*/ +/* class requests from USB 3.0 hub spec, table 10-5 */ +#define SetHubDepth (0x3000 | HUB_SET_DEPTH) +#define GetPortErrorCount (0x8000 | HUB_GET_PORT_ERR_COUNT) + /* * Generic bandwidth allocation constants/support */ -- cgit v1.2.3 From 7fc56f0d9908fe140a01387d59954e3d0a2e7744 Mon Sep 17 00:00:00 2001 From: Luo Andy Date: Tue, 23 Nov 2010 10:41:21 +0800 Subject: usb: gadget: langwell_udc: add usb test mode support This patch adds test mode support for Langwell gadget driver. Signed-off-by: Henry Yuan Signed-off-by: Andy Luo Signed-off-by: Greg Kroah-Hartman --- drivers/usb/gadget/langwell_udc.c | 23 +++++++++++++++++++++++ include/linux/usb/ch9.h | 10 ++++++++++ 2 files changed, 33 insertions(+) (limited to 'include/linux/usb') diff --git a/drivers/usb/gadget/langwell_udc.c b/drivers/usb/gadget/langwell_udc.c index b8ec954c0692..777972454e3e 100644 --- a/drivers/usb/gadget/langwell_udc.c +++ b/drivers/usb/gadget/langwell_udc.c @@ -2225,6 +2225,7 @@ static void handle_setup_packet(struct langwell_udc *dev, u16 wValue = le16_to_cpu(setup->wValue); u16 wIndex = le16_to_cpu(setup->wIndex); u16 wLength = le16_to_cpu(setup->wLength); + u32 portsc1; dev_vdbg(&dev->pdev->dev, "---> %s()\n", __func__); @@ -2313,6 +2314,28 @@ static void handle_setup_packet(struct langwell_udc *dev, dev->dev_status &= ~(1 << wValue); } break; + case USB_DEVICE_TEST_MODE: + dev_dbg(&dev->pdev->dev, "SETUP: TEST MODE\n"); + if ((wIndex & 0xff) || + (dev->gadget.speed != USB_SPEED_HIGH)) + ep0_stall(dev); + + switch (wIndex >> 8) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + case TEST_FORCE_EN: + if (prime_status_phase(dev, EP_DIR_IN)) + ep0_stall(dev); + portsc1 = readl(&dev->op_regs->portsc1); + portsc1 |= (wIndex & 0xf00) << 8; + writel(portsc1, &dev->op_regs->portsc1); + goto end; + default: + rc = -EOPNOTSUPP; + } + break; default: rc = -EOPNOTSUPP; break; diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h index f917bbbc8901..ab461948b579 100644 --- a/include/linux/usb/ch9.h +++ b/include/linux/usb/ch9.h @@ -123,6 +123,16 @@ #define USB_DEVICE_A_ALT_HNP_SUPPORT 5 /* (otg) other RH port does */ #define USB_DEVICE_DEBUG_MODE 6 /* (special devices only) */ +/* + * Test Mode Selectors + * See USB 2.0 spec Table 9-7 + */ +#define TEST_J 1 +#define TEST_K 2 +#define TEST_SE0_NAK 3 +#define TEST_PACKET 4 +#define TEST_FORCE_EN 5 + /* * New Feature Selectors as added by USB 3.0 * See USB 3.0 spec Table 9-6 -- cgit v1.2.3 From 05ac10dd6862a3fcce33d2203fbb2ef285e3ca87 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 2 Dec 2010 08:49:26 +0200 Subject: usb: musb: trivial search and replace patch change all ocurrences of musb_hdrc to musb-hdrc. We will call glue layer drivers musb-, so in order to keep things somewhat standard, let's change the underscore into a dash. Signed-off-by: Felipe Balbi --- arch/arm/mach-davinci/usb.c | 2 +- arch/arm/mach-omap2/clock2420_data.c | 2 +- arch/arm/mach-omap2/clock2430_data.c | 2 +- arch/arm/mach-omap2/clock3xxx_data.c | 8 ++++---- arch/arm/mach-omap2/clock44xx_data.c | 2 +- arch/arm/mach-omap2/usb-musb.c | 2 +- arch/arm/mach-omap2/usb-tusb6010.c | 2 +- arch/blackfin/mach-bf527/boards/ad7160eval.c | 2 +- arch/blackfin/mach-bf527/boards/cm_bf527.c | 2 +- arch/blackfin/mach-bf527/boards/ezbrd.c | 2 +- arch/blackfin/mach-bf527/boards/ezkit.c | 2 +- arch/blackfin/mach-bf527/boards/tll6527m.c | 2 +- arch/blackfin/mach-bf548/boards/cm_bf548.c | 2 +- arch/blackfin/mach-bf548/boards/ezkit.c | 2 +- drivers/usb/gadget/gadget_chips.h | 2 +- drivers/usb/musb/Kconfig | 2 +- drivers/usb/musb/musb_core.c | 2 +- include/linux/usb/musb.h | 2 +- 18 files changed, 21 insertions(+), 21 deletions(-) (limited to 'include/linux/usb') diff --git a/arch/arm/mach-davinci/usb.c b/arch/arm/mach-davinci/usb.c index b0d6b07431c0..0c7735bc0d19 100644 --- a/arch/arm/mach-davinci/usb.c +++ b/arch/arm/mach-davinci/usb.c @@ -76,7 +76,7 @@ static struct resource usb_resources[] = { static u64 usb_dmamask = DMA_BIT_MASK(32); static struct platform_device usb_dev = { - .name = "musb_hdrc", + .name = "musb-hdrc", .id = -1, .dev = { .platform_data = &usb_data, diff --git a/arch/arm/mach-omap2/clock2420_data.c b/arch/arm/mach-omap2/clock2420_data.c index 21f856252ad8..831e25c89d2d 100644 --- a/arch/arm/mach-omap2/clock2420_data.c +++ b/arch/arm/mach-omap2/clock2420_data.c @@ -1877,7 +1877,7 @@ static struct omap_clk omap2420_clks[] = { CLK("omap-aes", "ick", &aes_ick, CK_242X), CLK(NULL, "pka_ick", &pka_ick, CK_242X), CLK(NULL, "usb_fck", &usb_fck, CK_242X), - CLK("musb_hdrc", "fck", &osc_ck, CK_242X), + CLK("musb-hdrc", "fck", &osc_ck, CK_242X), }; /* diff --git a/arch/arm/mach-omap2/clock2430_data.c b/arch/arm/mach-omap2/clock2430_data.c index e32afcbdfb88..a6bccd7475a0 100644 --- a/arch/arm/mach-omap2/clock2430_data.c +++ b/arch/arm/mach-omap2/clock2430_data.c @@ -1983,7 +1983,7 @@ static struct omap_clk omap2430_clks[] = { CLK("omap-aes", "ick", &aes_ick, CK_243X), CLK(NULL, "pka_ick", &pka_ick, CK_243X), CLK(NULL, "usb_fck", &usb_fck, CK_243X), - CLK("musb_hdrc", "ick", &usbhs_ick, CK_243X), + CLK("musb-hdrc", "ick", &usbhs_ick, CK_243X), CLK("mmci-omap-hs.0", "ick", &mmchs1_ick, CK_243X), CLK("mmci-omap-hs.0", "fck", &mmchs1_fck, CK_243X), CLK("mmci-omap-hs.1", "ick", &mmchs2_ick, CK_243X), diff --git a/arch/arm/mach-omap2/clock3xxx_data.c b/arch/arm/mach-omap2/clock3xxx_data.c index a04cb03db51f..3e668edbf6fe 100644 --- a/arch/arm/mach-omap2/clock3xxx_data.c +++ b/arch/arm/mach-omap2/clock3xxx_data.c @@ -3306,8 +3306,8 @@ static struct omap_clk omap3xxx_clks[] = { CLK(NULL, "ssi_sst_fck", &ssi_sst_fck_3430es1, CK_3430ES1), CLK(NULL, "ssi_sst_fck", &ssi_sst_fck_3430es2, CK_3430ES2), CLK(NULL, "core_l3_ick", &core_l3_ick, CK_3XXX), - CLK("musb_hdrc", "ick", &hsotgusb_ick_3430es1, CK_3430ES1), - CLK("musb_hdrc", "ick", &hsotgusb_ick_3430es2, CK_3430ES2), + CLK("musb-hdrc", "ick", &hsotgusb_ick_3430es1, CK_3430ES1), + CLK("musb-hdrc", "ick", &hsotgusb_ick_3430es2, CK_3430ES2), CLK(NULL, "sdrc_ick", &sdrc_ick, CK_3XXX), CLK(NULL, "gpmc_fck", &gpmc_fck, CK_3XXX), CLK(NULL, "security_l3_ick", &security_l3_ick, CK_343X), @@ -3442,8 +3442,8 @@ static struct omap_clk omap3xxx_clks[] = { CLK("davinci_emac", "phy_clk", &emac_fck, CK_AM35XX), CLK("vpfe-capture", "master", &vpfe_ick, CK_AM35XX), CLK("vpfe-capture", "slave", &vpfe_fck, CK_AM35XX), - CLK("musb_hdrc", "ick", &hsotgusb_ick_am35xx, CK_AM35XX), - CLK("musb_hdrc", "fck", &hsotgusb_fck_am35xx, CK_AM35XX), + CLK("musb-hdrc", "ick", &hsotgusb_ick_am35xx, CK_AM35XX), + CLK("musb-hdrc", "fck", &hsotgusb_fck_am35xx, CK_AM35XX), CLK(NULL, "hecc_ck", &hecc_ck, CK_AM35XX), CLK(NULL, "uart4_ick", &uart4_ick_am35xx, CK_AM35XX), }; diff --git a/arch/arm/mach-omap2/clock44xx_data.c b/arch/arm/mach-omap2/clock44xx_data.c index f473e8922664..95dd34ce6ea1 100644 --- a/arch/arm/mach-omap2/clock44xx_data.c +++ b/arch/arm/mach-omap2/clock44xx_data.c @@ -2953,7 +2953,7 @@ static struct omap_clk omap44xx_clks[] = { CLK("ehci-omap.0", "usbhost_ick", &dummy_ck, CK_443X), CLK(NULL, "otg_60m_gfclk", &otg_60m_gfclk, CK_443X), CLK(NULL, "usb_otg_hs_xclk", &usb_otg_hs_xclk, CK_443X), - CLK("musb_hdrc", "ick", &usb_otg_hs_ick, CK_443X), + CLK("musb-hdrc", "ick", &usb_otg_hs_ick, CK_443X), CLK(NULL, "usb_phy_cm_clk32k", &usb_phy_cm_clk32k, CK_443X), CLK(NULL, "usb_tll_hs_usb_ch2_clk", &usb_tll_hs_usb_ch2_clk, CK_443X), CLK(NULL, "usb_tll_hs_usb_ch0_clk", &usb_tll_hs_usb_ch0_clk, CK_443X), diff --git a/arch/arm/mach-omap2/usb-musb.c b/arch/arm/mach-omap2/usb-musb.c index ed0e85c3764f..7d3ea53af3ab 100644 --- a/arch/arm/mach-omap2/usb-musb.c +++ b/arch/arm/mach-omap2/usb-musb.c @@ -77,7 +77,7 @@ static struct musb_hdrc_platform_data musb_plat = { static u64 musb_dmamask = DMA_BIT_MASK(32); static struct platform_device musb_device = { - .name = "musb_hdrc", + .name = "musb-hdrc", .id = -1, .dev = { .dma_mask = &musb_dmamask, diff --git a/arch/arm/mach-omap2/usb-tusb6010.c b/arch/arm/mach-omap2/usb-tusb6010.c index 64a0112b70a5..42389213b470 100644 --- a/arch/arm/mach-omap2/usb-tusb6010.c +++ b/arch/arm/mach-omap2/usb-tusb6010.c @@ -223,7 +223,7 @@ static struct resource tusb_resources[] = { static u64 tusb_dmamask = ~(u32)0; static struct platform_device tusb_device = { - .name = "musb_hdrc", + .name = "musb-hdrc", .id = -1, .dev = { .dma_mask = &tusb_dmamask, diff --git a/arch/blackfin/mach-bf527/boards/ad7160eval.c b/arch/blackfin/mach-bf527/boards/ad7160eval.c index fc767ac76381..ec9f2612be1d 100644 --- a/arch/blackfin/mach-bf527/boards/ad7160eval.c +++ b/arch/blackfin/mach-bf527/boards/ad7160eval.c @@ -83,7 +83,7 @@ static struct musb_hdrc_platform_data musb_plat = { static u64 musb_dmamask = ~(u32)0; static struct platform_device musb_device = { - .name = "musb_hdrc", + .name = "musb-hdrc", .id = 0, .dev = { .dma_mask = &musb_dmamask, diff --git a/arch/blackfin/mach-bf527/boards/cm_bf527.c b/arch/blackfin/mach-bf527/boards/cm_bf527.c index f714d7be35b9..a7627dee688e 100644 --- a/arch/blackfin/mach-bf527/boards/cm_bf527.c +++ b/arch/blackfin/mach-bf527/boards/cm_bf527.c @@ -120,7 +120,7 @@ static struct musb_hdrc_platform_data musb_plat = { static u64 musb_dmamask = ~(u32)0; static struct platform_device musb_device = { - .name = "musb_hdrc", + .name = "musb-hdrc", .id = 0, .dev = { .dma_mask = &musb_dmamask, diff --git a/arch/blackfin/mach-bf527/boards/ezbrd.c b/arch/blackfin/mach-bf527/boards/ezbrd.c index 315eec930604..d1df634e24c3 100644 --- a/arch/blackfin/mach-bf527/boards/ezbrd.c +++ b/arch/blackfin/mach-bf527/boards/ezbrd.c @@ -84,7 +84,7 @@ static struct musb_hdrc_platform_data musb_plat = { static u64 musb_dmamask = ~(u32)0; static struct platform_device musb_device = { - .name = "musb_hdrc", + .name = "musb-hdrc", .id = 0, .dev = { .dma_mask = &musb_dmamask, diff --git a/arch/blackfin/mach-bf527/boards/ezkit.c b/arch/blackfin/mach-bf527/boards/ezkit.c index 273731279740..5983abd8a7e3 100644 --- a/arch/blackfin/mach-bf527/boards/ezkit.c +++ b/arch/blackfin/mach-bf527/boards/ezkit.c @@ -124,7 +124,7 @@ static struct musb_hdrc_platform_data musb_plat = { static u64 musb_dmamask = ~(u32)0; static struct platform_device musb_device = { - .name = "musb_hdrc", + .name = "musb-hdrc", .id = 0, .dev = { .dma_mask = &musb_dmamask, diff --git a/arch/blackfin/mach-bf527/boards/tll6527m.c b/arch/blackfin/mach-bf527/boards/tll6527m.c index 9ec575729e2c..284ec1f44131 100644 --- a/arch/blackfin/mach-bf527/boards/tll6527m.c +++ b/arch/blackfin/mach-bf527/boards/tll6527m.c @@ -91,7 +91,7 @@ static struct musb_hdrc_platform_data musb_plat = { static u64 musb_dmamask = ~(u32)0; static struct platform_device musb_device = { - .name = "musb_hdrc", + .name = "musb-hdrc", .id = 0, .dev = { .dma_mask = &musb_dmamask, diff --git a/arch/blackfin/mach-bf548/boards/cm_bf548.c b/arch/blackfin/mach-bf548/boards/cm_bf548.c index 3e3dfb23f94e..f52334519a20 100644 --- a/arch/blackfin/mach-bf548/boards/cm_bf548.c +++ b/arch/blackfin/mach-bf548/boards/cm_bf548.c @@ -520,7 +520,7 @@ static struct musb_hdrc_platform_data musb_plat = { static u64 musb_dmamask = ~(u32)0; static struct platform_device musb_device = { - .name = "musb_hdrc", + .name = "musb-hdrc", .id = 0, .dev = { .dma_mask = &musb_dmamask, diff --git a/arch/blackfin/mach-bf548/boards/ezkit.c b/arch/blackfin/mach-bf548/boards/ezkit.c index 9ff166d6f00d..e2c851bef99a 100644 --- a/arch/blackfin/mach-bf548/boards/ezkit.c +++ b/arch/blackfin/mach-bf548/boards/ezkit.c @@ -625,7 +625,7 @@ static struct musb_hdrc_platform_data musb_plat = { static u64 musb_dmamask = ~(u32)0; static struct platform_device musb_device = { - .name = "musb_hdrc", + .name = "musb-hdrc", .id = 0, .dev = { .dma_mask = &musb_dmamask, diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index d7b3bbe56808..aeb109bd317d 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -96,7 +96,7 @@ /* Mentor high speed "dual role" controller, in peripheral role */ #ifdef CONFIG_USB_GADGET_MUSB_HDRC -#define gadget_is_musbhdrc(g) !strcmp("musb_hdrc", (g)->name) +#define gadget_is_musbhdrc(g) !strcmp("musb-hdrc", (g)->name) #else #define gadget_is_musbhdrc(g) 0 #endif diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index 7119d900a479..03a42901922c 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -30,7 +30,7 @@ config USB_MUSB_HDRC If you do not know what this is, please say N. To compile this driver as a module, choose M here; the - module will be called "musb_hdrc". + module will be called "musb-hdrc". choice prompt "Platform Glue Layer" diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 8df1c583f19d..1ca14f90c994 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -126,7 +126,7 @@ MODULE_PARM_DESC(debug, "Debug message level. Default = 0"); #define DRIVER_INFO DRIVER_DESC ", v" MUSB_VERSION -#define MUSB_DRIVER_NAME "musb_hdrc" +#define MUSB_DRIVER_NAME "musb-hdrc" const char musb_driver_name[] = MUSB_DRIVER_NAME; MODULE_DESCRIPTION(DRIVER_INFO); diff --git a/include/linux/usb/musb.h b/include/linux/usb/musb.h index ee2dd1d506ed..e2191d4db4dd 100644 --- a/include/linux/usb/musb.h +++ b/include/linux/usb/musb.h @@ -3,7 +3,7 @@ * Inventra (Multidrop) Highspeed Dual-Role Controllers: (M)HDRC. * * Board initialization should put one of these into dev->platform_data, - * probably on some platform_device named "musb_hdrc". It encapsulates + * probably on some platform_device named "musb-hdrc". It encapsulates * key configuration differences between boards. */ -- cgit v1.2.3 From f7ec94370f417fedad4db1054228ef958d48b926 Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 2 Dec 2010 09:48:58 +0200 Subject: usb: musb: pass platform_ops via platform_data ... then we don't need to export any symbols from glue layer to musb_core. Signed-off-by: Felipe Balbi --- drivers/usb/musb/am35x.c | 4 +++- drivers/usb/musb/blackfin.c | 4 +++- drivers/usb/musb/da8xx.c | 4 +++- drivers/usb/musb/davinci.c | 4 +++- drivers/usb/musb/musb_core.c | 2 +- drivers/usb/musb/musb_core.h | 2 -- drivers/usb/musb/omap2430.c | 4 +++- drivers/usb/musb/tusb6010.c | 4 +++- include/linux/usb/musb.h | 3 +++ 9 files changed, 22 insertions(+), 9 deletions(-) (limited to 'include/linux/usb') diff --git a/drivers/usb/musb/am35x.c b/drivers/usb/musb/am35x.c index 355883c5ffed..e372c87f37e2 100644 --- a/drivers/usb/musb/am35x.c +++ b/drivers/usb/musb/am35x.c @@ -530,7 +530,7 @@ void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) } } -const struct musb_platform_ops musb_ops = { +static const struct musb_platform_ops am35x_ops = { .init = am35x_musb_init, .exit = am35x_musb_exit, @@ -572,6 +572,8 @@ static int __init am35x_probe(struct platform_device *pdev) glue->dev = &pdev->dev; glue->musb = musb; + pdata->platform_ops = &am35x_ops; + platform_set_drvdata(pdev, glue); ret = platform_device_add_resources(musb, pdev->resource, diff --git a/drivers/usb/musb/blackfin.c b/drivers/usb/musb/blackfin.c index 02eded21d171..03cb001c0e19 100644 --- a/drivers/usb/musb/blackfin.c +++ b/drivers/usb/musb/blackfin.c @@ -436,7 +436,7 @@ static int bfin_musb_exit(struct musb *musb) return 0; } -const struct musb_platform_ops musb_ops = { +static const struct musb_platform_ops bfin_ops = { .init = bfin_musb_init, .exit = bfin_musb_exit, @@ -479,6 +479,8 @@ static int __init bfin_probe(struct platform_device *pdev) glue->dev = &pdev->dev; glue->musb = musb; + pdata->platform_ops = &bfin_ops; + platform_set_drvdata(pdev, glue); ret = platform_device_add_resources(musb, pdev->resource, diff --git a/drivers/usb/musb/da8xx.c b/drivers/usb/musb/da8xx.c index 94ddfe01d673..45ccac3aad9d 100644 --- a/drivers/usb/musb/da8xx.c +++ b/drivers/usb/musb/da8xx.c @@ -475,7 +475,7 @@ static int da8xx_musb_exit(struct musb *musb) return 0; } -const struct musb_platform_ops musb_ops = { +static const struct musb_platform_ops da8xx_ops = { .init = da8xx_musb_init, .exit = da8xx_musb_exit, @@ -517,6 +517,8 @@ static int __init da8xx_probe(struct platform_device *pdev) glue->dev = &pdev->dev; glue->musb = musb; + pdata->platform_ops = &da8xx_ops; + platform_set_drvdata(pdev, glue); ret = platform_device_add_resources(musb, pdev->resource, diff --git a/drivers/usb/musb/davinci.c b/drivers/usb/musb/davinci.c index 661870a1cd4d..831a04dd5a53 100644 --- a/drivers/usb/musb/davinci.c +++ b/drivers/usb/musb/davinci.c @@ -510,7 +510,7 @@ static int davinci_musb_exit(struct musb *musb) return 0; } -const struct musb_platform_ops musb_ops = { +static const struct musb_platform_ops davinci_ops = { .init = davinci_musb_init, .exit = davinci_musb_exit, @@ -551,6 +551,8 @@ static int __init davinci_probe(struct platform_device *pdev) glue->dev = &pdev->dev; glue->musb = musb; + pdata->platform_ops = &davinci_ops; + platform_set_drvdata(pdev, glue); ret = platform_device_add_resources(musb, pdev->resource, diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 1ca14f90c994..dcc77ef6cfff 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -1897,7 +1897,6 @@ allocate_instance(struct device *dev, } musb->controller = dev; - musb->ops = &musb_ops; return musb; } @@ -1997,6 +1996,7 @@ bad_config: musb->board_set_power = plat->set_power; musb->set_clock = plat->set_clock; musb->min_power = plat->min_power; + musb->ops = plat->platform_ops; /* Clock usage is chip-specific ... functional clock (DaVinci, * OMAP2430), or PHY ref (some TUSB6010 boards). All this core diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 9594b9d1d27b..fac1eab3c59f 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -281,8 +281,6 @@ struct musb_platform_ops { void (*set_vbus)(struct musb *musb, int on); }; -extern const struct musb_platform_ops musb_ops; - /* * struct musb_hw_ep - endpoint hardware (bidirectional) * diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index bca9df7557a4..2eea1703e630 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -333,7 +333,7 @@ static int omap2430_musb_exit(struct musb *musb) return 0; } -const struct musb_platform_ops musb_ops = { +static const struct musb_platform_ops omap2430_ops = { .init = omap2430_musb_init, .exit = omap2430_musb_exit, @@ -378,6 +378,8 @@ static int __init omap2430_probe(struct platform_device *pdev) glue->dev = &pdev->dev; glue->musb = musb; + pdata->platform_ops = &omap2430_ops; + platform_set_drvdata(pdev, glue); ret = platform_device_add_resources(musb, pdev->resource, diff --git a/drivers/usb/musb/tusb6010.c b/drivers/usb/musb/tusb6010.c index 2ff78d6b2eff..d6b832641c53 100644 --- a/drivers/usb/musb/tusb6010.c +++ b/drivers/usb/musb/tusb6010.c @@ -1178,7 +1178,7 @@ static int tusb_musb_exit(struct musb *musb) return 0; } -const struct musb_platform_ops musb_ops = { +static const struct musb_platform_ops tusb_ops = { .init = tusb_musb_init, .exit = tusb_musb_exit, @@ -1221,6 +1221,8 @@ static int __init tusb_probe(struct platform_device *pdev) glue->dev = &pdev->dev; glue->musb = musb; + pdata->platform_ops = &tusb_ops; + platform_set_drvdata(pdev, glue); ret = platform_device_add_resources(musb, pdev->resource, diff --git a/include/linux/usb/musb.h b/include/linux/usb/musb.h index e2191d4db4dd..6f4e5014cf5e 100644 --- a/include/linux/usb/musb.h +++ b/include/linux/usb/musb.h @@ -126,6 +126,9 @@ struct musb_hdrc_platform_data { /* Architecture specific board data */ void *board_data; + + /* Platform specific struct musb_ops pointer */ + const void *platform_ops; }; -- cgit v1.2.3 From 3b7029670d39d22f288ece95254e9ba5ceddd6ba Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 2 Dec 2010 09:51:00 +0200 Subject: usb: musb: mark ->set_clock deprecated ... we will completely drop that need by moving clock handling to platform glue layer. Marking as deprecated will allow us to catch all users easily. Signed-off-by: Felipe Balbi --- include/linux/usb/musb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/linux/usb') diff --git a/include/linux/usb/musb.h b/include/linux/usb/musb.h index 6f4e5014cf5e..0b72b5741645 100644 --- a/include/linux/usb/musb.h +++ b/include/linux/usb/musb.h @@ -119,7 +119,7 @@ struct musb_hdrc_platform_data { int (*set_power)(int state); /* Turn device clock on or off */ - int (*set_clock)(struct clk *clock, int is_on); + int (*set_clock)(struct clk *clock, int is_on) __deprecated; /* MUSB configuration-specific details */ struct musb_hdrc_config *config; -- cgit v1.2.3 From fa56df915d101770a495569473b4c13b1904087b Mon Sep 17 00:00:00 2001 From: Felipe Balbi Date: Thu, 2 Dec 2010 10:55:29 +0200 Subject: usb: musb: drop the set_clock magic now that platform glue layer handles clock completely, that function is completely useless for us. Drop it. Signed-off-by: Felipe Balbi --- drivers/usb/musb/musb_core.h | 2 -- include/linux/usb/musb.h | 3 --- 2 files changed, 5 deletions(-) (limited to 'include/linux/usb') diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 6c8e9630fb19..1e538675ab86 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -455,8 +455,6 @@ struct musb { u8 board_mode; /* enum musb_mode */ int (*board_set_power)(int state); - int (*set_clock)(struct clk *clk, int is_active); - u8 min_power; /* vbus for periph, in mA/2 */ bool is_host; diff --git a/include/linux/usb/musb.h b/include/linux/usb/musb.h index 0b72b5741645..0b0e383035e5 100644 --- a/include/linux/usb/musb.h +++ b/include/linux/usb/musb.h @@ -118,9 +118,6 @@ struct musb_hdrc_platform_data { /* Power the device on or off */ int (*set_power)(int state); - /* Turn device clock on or off */ - int (*set_clock)(struct clk *clock, int is_on) __deprecated; - /* MUSB configuration-specific details */ struct musb_hdrc_config *config; -- cgit v1.2.3 From 352a337832774a6929c16b569abe9cedc3db01cc Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Thu, 9 Dec 2010 22:46:29 +0100 Subject: USB: otg: fix link breakage, when the NOP USB Xceiver is a module If the NOP USB OTG transceiver driver is built as a module, the otg.h header declares external functions, but if they are referenced from the kernel proper, as, e.g., in the OMAP3 case, where the omap3evm board is calling the usb_nop_xceiv_register() function, linkage breaks. This patch fixes this problem. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Greg Kroah-Hartman --- include/linux/usb/otg.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/linux/usb') diff --git a/include/linux/usb/otg.h b/include/linux/usb/otg.h index 0a5b3711e502..a1a1e7a73ec9 100644 --- a/include/linux/usb/otg.h +++ b/include/linux/usb/otg.h @@ -116,7 +116,7 @@ struct otg_transceiver { /* for board-specific init logic */ extern int otg_set_transceiver(struct otg_transceiver *); -#if defined(CONFIG_NOP_USB_XCEIV) || defined(CONFIG_NOP_USB_XCEIV_MODULE) +#if defined(CONFIG_NOP_USB_XCEIV) || (defined(CONFIG_NOP_USB_XCEIV_MODULE) && defined(MODULE)) /* sometimes transceivers are accessed only through e.g. ULPI */ extern void usb_nop_xceiv_register(void); extern void usb_nop_xceiv_unregister(void); -- cgit v1.2.3 From e0c201f339fe7fc38d1b0f6f4755ff627686c7e0 Mon Sep 17 00:00:00 2001 From: Pavankumar Kondeti Date: Tue, 7 Dec 2010 17:53:55 +0530 Subject: USB: Add MSM OTG Controller driver This driver implements PHY initialization, clock management, ULPI IO ops and simple OTG state machine to kick host/peripheral based on Id/VBUS line status. VBUS/Id lines are tied to a reference voltage on some boards. Hence provide debugfs interface to select host/peripheral mode. Signed-off-by: Pavankumar Kondeti Signed-off-by: Greg Kroah-Hartman --- drivers/usb/otg/Kconfig | 10 + drivers/usb/otg/Makefile | 1 + drivers/usb/otg/msm72k_otg.c | 850 +++++++++++++++++++++++++++++++++++++++ include/linux/usb/msm_hsusb.h | 108 +++++ include/linux/usb/msm_hsusb_hw.h | 56 +++ 5 files changed, 1025 insertions(+) create mode 100644 drivers/usb/otg/msm72k_otg.c create mode 100644 include/linux/usb/msm_hsusb.h create mode 100644 include/linux/usb/msm_hsusb_hw.h (limited to 'include/linux/usb') diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index 5ce07528cd0c..915c729872f8 100644 --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -81,4 +81,14 @@ config USB_LANGWELL_OTG To compile this driver as a module, choose M here: the module will be called langwell_otg. +config USB_MSM_OTG_72K + tristate "OTG support for Qualcomm on-chip USB controller" + depends on (USB || USB_GADGET) && ARCH_MSM + select USB_OTG_UTILS + help + Enable this to support the USB OTG transceiver on MSM chips. It + handles PHY initialization, clock management, and workarounds + required after resetting the hardware. This driver is required + even for peripheral only or host only mode configuration. + endif # USB || OTG diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile index 66f1b83e4fa7..3b1b0960fb68 100644 --- a/drivers/usb/otg/Makefile +++ b/drivers/usb/otg/Makefile @@ -15,3 +15,4 @@ obj-$(CONFIG_TWL4030_USB) += twl4030-usb.o obj-$(CONFIG_USB_LANGWELL_OTG) += langwell_otg.o obj-$(CONFIG_NOP_USB_XCEIV) += nop-usb-xceiv.o obj-$(CONFIG_USB_ULPI) += ulpi.o +obj-$(CONFIG_USB_MSM_OTG_72K) += msm72k_otg.o diff --git a/drivers/usb/otg/msm72k_otg.c b/drivers/usb/otg/msm72k_otg.c new file mode 100644 index 000000000000..46f468a912f4 --- /dev/null +++ b/drivers/usb/otg/msm72k_otg.c @@ -0,0 +1,850 @@ +/* Copyright (c) 2009-2010, Code Aurora Forum. 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 version 2 and + * only 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MSM_USB_BASE (motg->regs) +#define DRIVER_NAME "msm_otg" + +#define ULPI_IO_TIMEOUT_USEC (10 * 1000) +static int ulpi_read(struct otg_transceiver *otg, u32 reg) +{ + struct msm_otg *motg = container_of(otg, struct msm_otg, otg); + int cnt = 0; + + /* initiate read operation */ + writel(ULPI_RUN | ULPI_READ | ULPI_ADDR(reg), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + while (cnt < ULPI_IO_TIMEOUT_USEC) { + if (!(readl(USB_ULPI_VIEWPORT) & ULPI_RUN)) + break; + udelay(1); + cnt++; + } + + if (cnt >= ULPI_IO_TIMEOUT_USEC) { + dev_err(otg->dev, "ulpi_read: timeout %08x\n", + readl(USB_ULPI_VIEWPORT)); + return -ETIMEDOUT; + } + return ULPI_DATA_READ(readl(USB_ULPI_VIEWPORT)); +} + +static int ulpi_write(struct otg_transceiver *otg, u32 val, u32 reg) +{ + struct msm_otg *motg = container_of(otg, struct msm_otg, otg); + int cnt = 0; + + /* initiate write operation */ + writel(ULPI_RUN | ULPI_WRITE | + ULPI_ADDR(reg) | ULPI_DATA(val), + USB_ULPI_VIEWPORT); + + /* wait for completion */ + while (cnt < ULPI_IO_TIMEOUT_USEC) { + if (!(readl(USB_ULPI_VIEWPORT) & ULPI_RUN)) + break; + udelay(1); + cnt++; + } + + if (cnt >= ULPI_IO_TIMEOUT_USEC) { + dev_err(otg->dev, "ulpi_write: timeout\n"); + return -ETIMEDOUT; + } + return 0; +} + +static struct otg_io_access_ops msm_otg_io_ops = { + .read = ulpi_read, + .write = ulpi_write, +}; + +static void ulpi_init(struct msm_otg *motg) +{ + struct msm_otg_platform_data *pdata = motg->pdata; + int *seq = pdata->phy_init_seq; + + if (!seq) + return; + + while (seq[0] >= 0) { + dev_vdbg(motg->otg.dev, "ulpi: write 0x%02x to 0x%02x\n", + seq[0], seq[1]); + ulpi_write(&motg->otg, seq[0], seq[1]); + seq += 2; + } +} + +static int msm_otg_link_clk_reset(struct msm_otg *motg, bool assert) +{ + int ret; + + if (assert) { + ret = clk_reset(motg->clk, CLK_RESET_ASSERT); + if (ret) + dev_err(motg->otg.dev, "usb hs_clk assert failed\n"); + } else { + ret = clk_reset(motg->clk, CLK_RESET_DEASSERT); + if (ret) + dev_err(motg->otg.dev, "usb hs_clk deassert failed\n"); + } + return ret; +} + +static int msm_otg_phy_clk_reset(struct msm_otg *motg) +{ + int ret; + + ret = clk_reset(motg->phy_reset_clk, CLK_RESET_ASSERT); + if (ret) { + dev_err(motg->otg.dev, "usb phy clk assert failed\n"); + return ret; + } + usleep_range(10000, 12000); + ret = clk_reset(motg->phy_reset_clk, CLK_RESET_DEASSERT); + if (ret) + dev_err(motg->otg.dev, "usb phy clk deassert failed\n"); + return ret; +} + +static int msm_otg_phy_reset(struct msm_otg *motg) +{ + u32 val; + int ret; + int retries; + + ret = msm_otg_link_clk_reset(motg, 1); + if (ret) + return ret; + ret = msm_otg_phy_clk_reset(motg); + if (ret) + return ret; + ret = msm_otg_link_clk_reset(motg, 0); + if (ret) + return ret; + + val = readl(USB_PORTSC) & ~PORTSC_PTS_MASK; + writel(val | PORTSC_PTS_ULPI, USB_PORTSC); + + for (retries = 3; retries > 0; retries--) { + ret = ulpi_write(&motg->otg, ULPI_FUNC_CTRL_SUSPENDM, + ULPI_CLR(ULPI_FUNC_CTRL)); + if (!ret) + break; + ret = msm_otg_phy_clk_reset(motg); + if (ret) + return ret; + } + if (!retries) + return -ETIMEDOUT; + + /* This reset calibrates the phy, if the above write succeeded */ + ret = msm_otg_phy_clk_reset(motg); + if (ret) + return ret; + + for (retries = 3; retries > 0; retries--) { + ret = ulpi_read(&motg->otg, ULPI_DEBUG); + if (ret != -ETIMEDOUT) + break; + ret = msm_otg_phy_clk_reset(motg); + if (ret) + return ret; + } + if (!retries) + return -ETIMEDOUT; + + dev_info(motg->otg.dev, "phy_reset: success\n"); + return 0; +} + +#define LINK_RESET_TIMEOUT_USEC (250 * 1000) +static int msm_otg_reset(struct otg_transceiver *otg) +{ + struct msm_otg *motg = container_of(otg, struct msm_otg, otg); + struct msm_otg_platform_data *pdata = motg->pdata; + int cnt = 0; + int ret; + u32 val = 0; + u32 ulpi_val = 0; + + ret = msm_otg_phy_reset(motg); + if (ret) { + dev_err(otg->dev, "phy_reset failed\n"); + return ret; + } + + ulpi_init(motg); + + writel(USBCMD_RESET, USB_USBCMD); + while (cnt < LINK_RESET_TIMEOUT_USEC) { + if (!(readl(USB_USBCMD) & USBCMD_RESET)) + break; + udelay(1); + cnt++; + } + if (cnt >= LINK_RESET_TIMEOUT_USEC) + return -ETIMEDOUT; + + /* select ULPI phy */ + writel(0x80000000, USB_PORTSC); + + msleep(100); + + writel(0x0, USB_AHBBURST); + writel(0x00, USB_AHBMODE); + + if (pdata->otg_control == OTG_PHY_CONTROL) { + val = readl(USB_OTGSC); + if (pdata->mode == USB_OTG) { + ulpi_val = ULPI_INT_IDGRD | ULPI_INT_SESS_VALID; + val |= OTGSC_IDIE | OTGSC_BSVIE; + } else if (pdata->mode == USB_PERIPHERAL) { + ulpi_val = ULPI_INT_SESS_VALID; + val |= OTGSC_BSVIE; + } + writel(val, USB_OTGSC); + ulpi_write(otg, ulpi_val, ULPI_USB_INT_EN_RISE); + ulpi_write(otg, ulpi_val, ULPI_USB_INT_EN_FALL); + } + + return 0; +} + +static void msm_otg_start_host(struct otg_transceiver *otg, int on) +{ + struct msm_otg *motg = container_of(otg, struct msm_otg, otg); + struct msm_otg_platform_data *pdata = motg->pdata; + struct usb_hcd *hcd; + + if (!otg->host) + return; + + hcd = bus_to_hcd(otg->host); + + if (on) { + dev_dbg(otg->dev, "host on\n"); + + if (pdata->vbus_power) + pdata->vbus_power(1); + /* + * Some boards have a switch cotrolled by gpio + * to enable/disable internal HUB. Enable internal + * HUB before kicking the host. + */ + if (pdata->setup_gpio) + pdata->setup_gpio(OTG_STATE_A_HOST); +#ifdef CONFIG_USB + usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); +#endif + } else { + dev_dbg(otg->dev, "host off\n"); + +#ifdef CONFIG_USB + usb_remove_hcd(hcd); +#endif + if (pdata->setup_gpio) + pdata->setup_gpio(OTG_STATE_UNDEFINED); + if (pdata->vbus_power) + pdata->vbus_power(0); + } +} + +static int msm_otg_set_host(struct otg_transceiver *otg, struct usb_bus *host) +{ + struct msm_otg *motg = container_of(otg, struct msm_otg, otg); + struct usb_hcd *hcd; + + /* + * Fail host registration if this board can support + * only peripheral configuration. + */ + if (motg->pdata->mode == USB_PERIPHERAL) { + dev_info(otg->dev, "Host mode is not supported\n"); + return -ENODEV; + } + + if (!host) { + if (otg->state == OTG_STATE_A_HOST) { + msm_otg_start_host(otg, 0); + otg->host = NULL; + otg->state = OTG_STATE_UNDEFINED; + schedule_work(&motg->sm_work); + } else { + otg->host = NULL; + } + + return 0; + } + + hcd = bus_to_hcd(host); + hcd->power_budget = motg->pdata->power_budget; + + otg->host = host; + dev_dbg(otg->dev, "host driver registered w/ tranceiver\n"); + + /* + * Kick the state machine work, if peripheral is not supported + * or peripheral is already registered with us. + */ + if (motg->pdata->mode == USB_HOST || otg->gadget) + schedule_work(&motg->sm_work); + + return 0; +} + +static void msm_otg_start_peripheral(struct otg_transceiver *otg, int on) +{ + struct msm_otg *motg = container_of(otg, struct msm_otg, otg); + struct msm_otg_platform_data *pdata = motg->pdata; + + if (!otg->gadget) + return; + + if (on) { + dev_dbg(otg->dev, "gadget on\n"); + /* + * Some boards have a switch cotrolled by gpio + * to enable/disable internal HUB. Disable internal + * HUB before kicking the gadget. + */ + if (pdata->setup_gpio) + pdata->setup_gpio(OTG_STATE_B_PERIPHERAL); + usb_gadget_vbus_connect(otg->gadget); + } else { + dev_dbg(otg->dev, "gadget off\n"); + usb_gadget_vbus_disconnect(otg->gadget); + if (pdata->setup_gpio) + pdata->setup_gpio(OTG_STATE_UNDEFINED); + } + +} + +static int msm_otg_set_peripheral(struct otg_transceiver *otg, + struct usb_gadget *gadget) +{ + struct msm_otg *motg = container_of(otg, struct msm_otg, otg); + + /* + * Fail peripheral registration if this board can support + * only host configuration. + */ + if (motg->pdata->mode == USB_HOST) { + dev_info(otg->dev, "Peripheral mode is not supported\n"); + return -ENODEV; + } + + if (!gadget) { + if (otg->state == OTG_STATE_B_PERIPHERAL) { + msm_otg_start_peripheral(otg, 0); + otg->gadget = NULL; + otg->state = OTG_STATE_UNDEFINED; + schedule_work(&motg->sm_work); + } else { + otg->gadget = NULL; + } + + return 0; + } + otg->gadget = gadget; + dev_dbg(otg->dev, "peripheral driver registered w/ tranceiver\n"); + + /* + * Kick the state machine work, if host is not supported + * or host is already registered with us. + */ + if (motg->pdata->mode == USB_PERIPHERAL || otg->host) + schedule_work(&motg->sm_work); + + return 0; +} + +/* + * We support OTG, Peripheral only and Host only configurations. In case + * of OTG, mode switch (host-->peripheral/peripheral-->host) can happen + * via Id pin status or user request (debugfs). Id/BSV interrupts are not + * enabled when switch is controlled by user and default mode is supplied + * by board file, which can be changed by userspace later. + */ +static void msm_otg_init_sm(struct msm_otg *motg) +{ + struct msm_otg_platform_data *pdata = motg->pdata; + u32 otgsc = readl(USB_OTGSC); + + switch (pdata->mode) { + case USB_OTG: + if (pdata->otg_control == OTG_PHY_CONTROL) { + if (otgsc & OTGSC_ID) + set_bit(ID, &motg->inputs); + else + clear_bit(ID, &motg->inputs); + + if (otgsc & OTGSC_BSV) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); + } else if (pdata->otg_control == OTG_USER_CONTROL) { + if (pdata->default_mode == USB_HOST) { + clear_bit(ID, &motg->inputs); + } else if (pdata->default_mode == USB_PERIPHERAL) { + set_bit(ID, &motg->inputs); + set_bit(B_SESS_VLD, &motg->inputs); + } else { + set_bit(ID, &motg->inputs); + clear_bit(B_SESS_VLD, &motg->inputs); + } + } + break; + case USB_HOST: + clear_bit(ID, &motg->inputs); + break; + case USB_PERIPHERAL: + set_bit(ID, &motg->inputs); + if (otgsc & OTGSC_BSV) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); + break; + default: + break; + } +} + +static void msm_otg_sm_work(struct work_struct *w) +{ + struct msm_otg *motg = container_of(w, struct msm_otg, sm_work); + struct otg_transceiver *otg = &motg->otg; + + switch (otg->state) { + case OTG_STATE_UNDEFINED: + dev_dbg(otg->dev, "OTG_STATE_UNDEFINED state\n"); + msm_otg_reset(otg); + msm_otg_init_sm(motg); + otg->state = OTG_STATE_B_IDLE; + /* FALL THROUGH */ + case OTG_STATE_B_IDLE: + dev_dbg(otg->dev, "OTG_STATE_B_IDLE state\n"); + if (!test_bit(ID, &motg->inputs) && otg->host) { + /* disable BSV bit */ + writel(readl(USB_OTGSC) & ~OTGSC_BSVIE, USB_OTGSC); + msm_otg_start_host(otg, 1); + otg->state = OTG_STATE_A_HOST; + } else if (test_bit(B_SESS_VLD, &motg->inputs) && otg->gadget) { + msm_otg_start_peripheral(otg, 1); + otg->state = OTG_STATE_B_PERIPHERAL; + } + break; + case OTG_STATE_B_PERIPHERAL: + dev_dbg(otg->dev, "OTG_STATE_B_PERIPHERAL state\n"); + if (!test_bit(B_SESS_VLD, &motg->inputs) || + !test_bit(ID, &motg->inputs)) { + msm_otg_start_peripheral(otg, 0); + otg->state = OTG_STATE_B_IDLE; + msm_otg_reset(otg); + schedule_work(w); + } + break; + case OTG_STATE_A_HOST: + dev_dbg(otg->dev, "OTG_STATE_A_HOST state\n"); + if (test_bit(ID, &motg->inputs)) { + msm_otg_start_host(otg, 0); + otg->state = OTG_STATE_B_IDLE; + msm_otg_reset(otg); + schedule_work(w); + } + break; + default: + break; + } +} + +static irqreturn_t msm_otg_irq(int irq, void *data) +{ + struct msm_otg *motg = data; + struct otg_transceiver *otg = &motg->otg; + u32 otgsc = 0; + + otgsc = readl(USB_OTGSC); + if (!(otgsc & (OTGSC_IDIS | OTGSC_BSVIS))) + return IRQ_NONE; + + if ((otgsc & OTGSC_IDIS) && (otgsc & OTGSC_IDIE)) { + if (otgsc & OTGSC_ID) + set_bit(ID, &motg->inputs); + else + clear_bit(ID, &motg->inputs); + dev_dbg(otg->dev, "ID set/clear\n"); + } else if ((otgsc & OTGSC_BSVIS) && (otgsc & OTGSC_BSVIE)) { + if (otgsc & OTGSC_BSV) + set_bit(B_SESS_VLD, &motg->inputs); + else + clear_bit(B_SESS_VLD, &motg->inputs); + dev_dbg(otg->dev, "BSV set/clear\n"); + } + + writel(otgsc, USB_OTGSC); + schedule_work(&motg->sm_work); + return IRQ_HANDLED; +} + +static int msm_otg_mode_show(struct seq_file *s, void *unused) +{ + struct msm_otg *motg = s->private; + struct otg_transceiver *otg = &motg->otg; + + switch (otg->state) { + case OTG_STATE_A_HOST: + seq_printf(s, "host\n"); + break; + case OTG_STATE_B_PERIPHERAL: + seq_printf(s, "peripheral\n"); + break; + default: + seq_printf(s, "none\n"); + break; + } + + return 0; +} + +static int msm_otg_mode_open(struct inode *inode, struct file *file) +{ + return single_open(file, msm_otg_mode_show, inode->i_private); +} + +static ssize_t msm_otg_mode_write(struct file *file, const char __user *ubuf, + size_t count, loff_t *ppos) +{ + struct msm_otg *motg = file->private_data; + char buf[16]; + struct otg_transceiver *otg = &motg->otg; + int status = count; + enum usb_mode_type req_mode; + + memset(buf, 0x00, sizeof(buf)); + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) { + status = -EFAULT; + goto out; + } + + if (!strncmp(buf, "host", 4)) { + req_mode = USB_HOST; + } else if (!strncmp(buf, "peripheral", 10)) { + req_mode = USB_PERIPHERAL; + } else if (!strncmp(buf, "none", 4)) { + req_mode = USB_NONE; + } else { + status = -EINVAL; + goto out; + } + + switch (req_mode) { + case USB_NONE: + switch (otg->state) { + case OTG_STATE_A_HOST: + case OTG_STATE_B_PERIPHERAL: + set_bit(ID, &motg->inputs); + clear_bit(B_SESS_VLD, &motg->inputs); + break; + default: + goto out; + } + break; + case USB_PERIPHERAL: + switch (otg->state) { + case OTG_STATE_B_IDLE: + case OTG_STATE_A_HOST: + set_bit(ID, &motg->inputs); + set_bit(B_SESS_VLD, &motg->inputs); + break; + default: + goto out; + } + break; + case USB_HOST: + switch (otg->state) { + case OTG_STATE_B_IDLE: + case OTG_STATE_B_PERIPHERAL: + clear_bit(ID, &motg->inputs); + break; + default: + goto out; + } + break; + default: + goto out; + } + + schedule_work(&motg->sm_work); +out: + return status; +} + +const struct file_operations msm_otg_mode_fops = { + .open = msm_otg_mode_open, + .read = seq_read, + .write = msm_otg_mode_write, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *msm_otg_dbg_root; +static struct dentry *msm_otg_dbg_mode; + +static int msm_otg_debugfs_init(struct msm_otg *motg) +{ + msm_otg_dbg_root = debugfs_create_dir("msm_otg", NULL); + + if (!msm_otg_dbg_root || IS_ERR(msm_otg_dbg_root)) + return -ENODEV; + + msm_otg_dbg_mode = debugfs_create_file("mode", S_IRUGO | S_IWUSR, + msm_otg_dbg_root, motg, &msm_otg_mode_fops); + if (!msm_otg_dbg_mode) { + debugfs_remove(msm_otg_dbg_root); + msm_otg_dbg_root = NULL; + return -ENODEV; + } + + return 0; +} + +static void msm_otg_debugfs_cleanup(void) +{ + debugfs_remove(msm_otg_dbg_mode); + debugfs_remove(msm_otg_dbg_root); +} + +static int __init msm_otg_probe(struct platform_device *pdev) +{ + int ret = 0; + struct resource *res; + struct msm_otg *motg; + struct otg_transceiver *otg; + + dev_info(&pdev->dev, "msm_otg probe\n"); + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "No platform data given. Bailing out\n"); + return -ENODEV; + } + + motg = kzalloc(sizeof(struct msm_otg), GFP_KERNEL); + if (!motg) { + dev_err(&pdev->dev, "unable to allocate msm_otg\n"); + return -ENOMEM; + } + + motg->pdata = pdev->dev.platform_data; + otg = &motg->otg; + otg->dev = &pdev->dev; + + motg->phy_reset_clk = clk_get(&pdev->dev, "usb_phy_clk"); + if (IS_ERR(motg->phy_reset_clk)) { + dev_err(&pdev->dev, "failed to get usb_phy_clk\n"); + ret = PTR_ERR(motg->phy_reset_clk); + goto free_motg; + } + + motg->clk = clk_get(&pdev->dev, "usb_hs_clk"); + if (IS_ERR(motg->clk)) { + dev_err(&pdev->dev, "failed to get usb_hs_clk\n"); + ret = PTR_ERR(motg->clk); + goto put_phy_reset_clk; + } + + motg->pclk = clk_get(&pdev->dev, "usb_hs_pclk"); + if (IS_ERR(motg->pclk)) { + dev_err(&pdev->dev, "failed to get usb_hs_pclk\n"); + ret = PTR_ERR(motg->pclk); + goto put_clk; + } + + /* + * USB core clock is not present on all MSM chips. This + * clock is introduced to remove the dependency on AXI + * bus frequency. + */ + motg->core_clk = clk_get(&pdev->dev, "usb_hs_core_clk"); + if (IS_ERR(motg->core_clk)) + motg->core_clk = NULL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "failed to get platform resource mem\n"); + ret = -ENODEV; + goto put_core_clk; + } + + motg->regs = ioremap(res->start, resource_size(res)); + if (!motg->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto put_core_clk; + } + dev_info(&pdev->dev, "OTG regs = %p\n", motg->regs); + + motg->irq = platform_get_irq(pdev, 0); + if (!motg->irq) { + dev_err(&pdev->dev, "platform_get_irq failed\n"); + ret = -ENODEV; + goto free_regs; + } + + clk_enable(motg->clk); + clk_enable(motg->pclk); + if (motg->core_clk) + clk_enable(motg->core_clk); + + writel(0, USB_USBINTR); + writel(0, USB_OTGSC); + + INIT_WORK(&motg->sm_work, msm_otg_sm_work); + ret = request_irq(motg->irq, msm_otg_irq, IRQF_SHARED, + "msm_otg", motg); + if (ret) { + dev_err(&pdev->dev, "request irq failed\n"); + goto disable_clks; + } + + otg->init = msm_otg_reset; + otg->set_host = msm_otg_set_host; + otg->set_peripheral = msm_otg_set_peripheral; + + otg->io_ops = &msm_otg_io_ops; + + ret = otg_set_transceiver(&motg->otg); + if (ret) { + dev_err(&pdev->dev, "otg_set_transceiver failed\n"); + goto free_irq; + } + + platform_set_drvdata(pdev, motg); + device_init_wakeup(&pdev->dev, 1); + + if (motg->pdata->mode == USB_OTG && + motg->pdata->otg_control == OTG_USER_CONTROL) { + ret = msm_otg_debugfs_init(motg); + if (ret) + dev_dbg(&pdev->dev, "mode debugfs file is" + "not available\n"); + } + + return 0; + +free_irq: + free_irq(motg->irq, motg); +disable_clks: + clk_disable(motg->pclk); + clk_disable(motg->clk); +free_regs: + iounmap(motg->regs); +put_core_clk: + if (motg->core_clk) + clk_put(motg->core_clk); + clk_put(motg->pclk); +put_clk: + clk_put(motg->clk); +put_phy_reset_clk: + clk_put(motg->phy_reset_clk); +free_motg: + kfree(motg); + return ret; +} + +static int __devexit msm_otg_remove(struct platform_device *pdev) +{ + struct msm_otg *motg = platform_get_drvdata(pdev); + struct otg_transceiver *otg = &motg->otg; + + if (otg->host || otg->gadget) + return -EBUSY; + + msm_otg_debugfs_cleanup(); + cancel_work_sync(&motg->sm_work); + device_init_wakeup(&pdev->dev, 0); + otg_set_transceiver(NULL); + + free_irq(motg->irq, motg); + + clk_disable(motg->pclk); + clk_disable(motg->clk); + if (motg->core_clk) + clk_disable(motg->core_clk); + + iounmap(motg->regs); + + clk_put(motg->phy_reset_clk); + clk_put(motg->pclk); + clk_put(motg->clk); + if (motg->core_clk) + clk_put(motg->core_clk); + + kfree(motg); + + return 0; +} + +static struct platform_driver msm_otg_driver = { + .remove = __devexit_p(msm_otg_remove), + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init msm_otg_init(void) +{ + return platform_driver_probe(&msm_otg_driver, msm_otg_probe); +} + +static void __exit msm_otg_exit(void) +{ + platform_driver_unregister(&msm_otg_driver); +} + +module_init(msm_otg_init); +module_exit(msm_otg_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM USB transceiver driver"); diff --git a/include/linux/usb/msm_hsusb.h b/include/linux/usb/msm_hsusb.h new file mode 100644 index 000000000000..b796df94ec72 --- /dev/null +++ b/include/linux/usb/msm_hsusb.h @@ -0,0 +1,108 @@ +/* linux/include/asm-arm/arch-msm/hsusb.h + * + * Copyright (C) 2008 Google, Inc. + * Author: Brian Swetland + * Copyright (c) 2009-2010, Code Aurora Forum. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __ASM_ARCH_MSM_HSUSB_H +#define __ASM_ARCH_MSM_HSUSB_H + +#include +#include + +/** + * Supported USB modes + * + * USB_PERIPHERAL Only peripheral mode is supported. + * USB_HOST Only host mode is supported. + * USB_OTG OTG mode is supported. + * + */ +enum usb_mode_type { + USB_NONE = 0, + USB_PERIPHERAL, + USB_HOST, + USB_OTG, +}; + +/** + * OTG control + * + * OTG_NO_CONTROL Id/VBUS notifications not required. Useful in host + * only configuration. + * OTG_PHY_CONTROL Id/VBUS notifications comes form USB PHY. + * OTG_PMIC_CONTROL Id/VBUS notifications comes from PMIC hardware. + * OTG_USER_CONTROL Id/VBUS notifcations comes from User via sysfs. + * + */ +enum otg_control_type { + OTG_NO_CONTROL = 0, + OTG_PHY_CONTROL, + OTG_PMIC_CONTROL, + OTG_USER_CONTROL, +}; + +/** + * struct msm_otg_platform_data - platform device data + * for msm72k_otg driver. + * @phy_init_seq: PHY configuration sequence. val, reg pairs + * terminated by -1. + * @vbus_power: VBUS power on/off routine. + * @power_budget: VBUS power budget in mA (0 will be treated as 500mA). + * @mode: Supported mode (OTG/peripheral/host). + * @otg_control: OTG switch controlled by user/Id pin + * @default_mode: Default operational mode. Applicable only if + * OTG switch is controller by user. + * + */ +struct msm_otg_platform_data { + int *phy_init_seq; + void (*vbus_power)(bool on); + unsigned power_budget; + enum usb_mode_type mode; + enum otg_control_type otg_control; + enum usb_mode_type default_mode; + void (*setup_gpio)(enum usb_otg_state state); +}; + +/** + * struct msm_otg: OTG driver data. Shared by HCD and DCD. + * @otg: USB OTG Transceiver structure. + * @pdata: otg device platform data. + * @irq: IRQ number assigned for HSUSB controller. + * @clk: clock struct of usb_hs_clk. + * @pclk: clock struct of usb_hs_pclk. + * @phy_reset_clk: clock struct of usb_phy_clk. + * @core_clk: clock struct of usb_hs_core_clk. + * @regs: ioremapped register base address. + * @inputs: OTG state machine inputs(Id, SessValid etc). + * @sm_work: OTG state machine work. + * + */ +struct msm_otg { + struct otg_transceiver otg; + struct msm_otg_platform_data *pdata; + int irq; + struct clk *clk; + struct clk *pclk; + struct clk *phy_reset_clk; + struct clk *core_clk; + void __iomem *regs; +#define ID 0 +#define B_SESS_VLD 1 + unsigned long inputs; + struct work_struct sm_work; +}; + +#endif diff --git a/include/linux/usb/msm_hsusb_hw.h b/include/linux/usb/msm_hsusb_hw.h new file mode 100644 index 000000000000..b061cffcf048 --- /dev/null +++ b/include/linux/usb/msm_hsusb_hw.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2007 Google, Inc. + * Author: Brian Swetland + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#ifndef __LINUX_USB_GADGET_MSM72K_UDC_H__ +#define __LINUX_USB_GADGET_MSM72K_UDC_H__ + +#ifdef CONFIG_ARCH_MSM7X00A +#define USB_SBUSCFG (MSM_USB_BASE + 0x0090) +#else +#define USB_AHBBURST (MSM_USB_BASE + 0x0090) +#define USB_AHBMODE (MSM_USB_BASE + 0x0098) +#endif +#define USB_CAPLENGTH (MSM_USB_BASE + 0x0100) /* 8 bit */ + +#define USB_USBCMD (MSM_USB_BASE + 0x0140) +#define USB_PORTSC (MSM_USB_BASE + 0x0184) +#define USB_OTGSC (MSM_USB_BASE + 0x01A4) +#define USB_USBMODE (MSM_USB_BASE + 0x01A8) + +#define USBCMD_RESET 2 +#define USB_USBINTR (MSM_USB_BASE + 0x0148) + +#define PORTSC_PHCD (1 << 23) /* phy suspend mode */ +#define PORTSC_PTS_MASK (3 << 30) +#define PORTSC_PTS_ULPI (3 << 30) + +#define USB_ULPI_VIEWPORT (MSM_USB_BASE + 0x0170) +#define ULPI_RUN (1 << 30) +#define ULPI_WRITE (1 << 29) +#define ULPI_READ (0 << 29) +#define ULPI_ADDR(n) (((n) & 255) << 16) +#define ULPI_DATA(n) ((n) & 255) +#define ULPI_DATA_READ(n) (((n) >> 8) & 255) + +/* OTG definitions */ +#define OTGSC_INTSTS_MASK (0x7f << 16) +#define OTGSC_ID (1 << 8) +#define OTGSC_BSV (1 << 11) +#define OTGSC_IDIS (1 << 16) +#define OTGSC_BSVIS (1 << 19) +#define OTGSC_IDIE (1 << 24) +#define OTGSC_BSVIE (1 << 27) + +#endif /* __LINUX_USB_GADGET_MSM72K_UDC_H__ */ -- cgit v1.2.3 From 87c0104af742af2acfcbd685f2b9a40f33770dc0 Mon Sep 17 00:00:00 2001 From: Pavankumar Kondeti Date: Tue, 7 Dec 2010 17:53:58 +0530 Subject: USB: OTG: msm: Add support for power management Implement runtime and system pm ops to put hardware into low power mode (LPM). As part of LPM, USB clocks are turned off, PHY is put into suspend state and PHY comparators are turned off if VBUS/Id notifications are not required from PHY. Signed-off-by: Pavankumar Kondeti Signed-off-by: Greg Kroah-Hartman --- drivers/usb/otg/Kconfig | 5 +- drivers/usb/otg/msm72k_otg.c | 283 ++++++++++++++++++++++++++++++++++++++- include/linux/usb/msm_hsusb.h | 4 + include/linux/usb/msm_hsusb_hw.h | 3 + 4 files changed, 289 insertions(+), 6 deletions(-) (limited to 'include/linux/usb') diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig index 915c729872f8..2810c2af71b0 100644 --- a/drivers/usb/otg/Kconfig +++ b/drivers/usb/otg/Kconfig @@ -88,7 +88,8 @@ config USB_MSM_OTG_72K help Enable this to support the USB OTG transceiver on MSM chips. It handles PHY initialization, clock management, and workarounds - required after resetting the hardware. This driver is required - even for peripheral only or host only mode configuration. + required after resetting the hardware and power management. + This driver is required even for peripheral only or host only + mode configurations. endif # USB || OTG diff --git a/drivers/usb/otg/msm72k_otg.c b/drivers/usb/otg/msm72k_otg.c index 46f468a912f4..1cd52edcd0c2 100644 --- a/drivers/usb/otg/msm72k_otg.c +++ b/drivers/usb/otg/msm72k_otg.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -251,6 +252,154 @@ static int msm_otg_reset(struct otg_transceiver *otg) return 0; } +#define PHY_SUSPEND_TIMEOUT_USEC (500 * 1000) +static int msm_otg_suspend(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + struct usb_bus *bus = otg->host; + struct msm_otg_platform_data *pdata = motg->pdata; + int cnt = 0; + + if (atomic_read(&motg->in_lpm)) + return 0; + + disable_irq(motg->irq); + /* + * Interrupt Latch Register auto-clear feature is not present + * in all PHY versions. Latch register is clear on read type. + * Clear latch register to avoid spurious wakeup from + * low power mode (LPM). + */ + ulpi_read(otg, 0x14); + + /* + * PHY comparators are disabled when PHY enters into low power + * mode (LPM). Keep PHY comparators ON in LPM only when we expect + * VBUS/Id notifications from USB PHY. Otherwise turn off USB + * PHY comparators. This save significant amount of power. + */ + if (pdata->otg_control == OTG_PHY_CONTROL) + ulpi_write(otg, 0x01, 0x30); + + /* + * PLL is not turned off when PHY enters into low power mode (LPM). + * Disable PLL for maximum power savings. + */ + ulpi_write(otg, 0x08, 0x09); + + /* + * PHY may take some time or even fail to enter into low power + * mode (LPM). Hence poll for 500 msec and reset the PHY and link + * in failure case. + */ + writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); + while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { + if (readl(USB_PORTSC) & PORTSC_PHCD) + break; + udelay(1); + cnt++; + } + + if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) { + dev_err(otg->dev, "Unable to suspend PHY\n"); + msm_otg_reset(otg); + enable_irq(motg->irq); + return -ETIMEDOUT; + } + + /* + * PHY has capability to generate interrupt asynchronously in low + * power mode (LPM). This interrupt is level triggered. So USB IRQ + * line must be disabled till async interrupt enable bit is cleared + * in USBCMD register. Assert STP (ULPI interface STOP signal) to + * block data communication from PHY. + */ + writel(readl(USB_USBCMD) | ASYNC_INTR_CTRL | ULPI_STP_CTRL, USB_USBCMD); + + clk_disable(motg->pclk); + clk_disable(motg->clk); + if (motg->core_clk) + clk_disable(motg->core_clk); + + if (device_may_wakeup(otg->dev)) + enable_irq_wake(motg->irq); + if (bus) + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); + + atomic_set(&motg->in_lpm, 1); + enable_irq(motg->irq); + + dev_info(otg->dev, "USB in low power mode\n"); + + return 0; +} + +#define PHY_RESUME_TIMEOUT_USEC (100 * 1000) +static int msm_otg_resume(struct msm_otg *motg) +{ + struct otg_transceiver *otg = &motg->otg; + struct usb_bus *bus = otg->host; + int cnt = 0; + unsigned temp; + + if (!atomic_read(&motg->in_lpm)) + return 0; + + clk_enable(motg->pclk); + clk_enable(motg->clk); + if (motg->core_clk) + clk_enable(motg->core_clk); + + temp = readl(USB_USBCMD); + temp &= ~ASYNC_INTR_CTRL; + temp &= ~ULPI_STP_CTRL; + writel(temp, USB_USBCMD); + + /* + * PHY comes out of low power mode (LPM) in case of wakeup + * from asynchronous interrupt. + */ + if (!(readl(USB_PORTSC) & PORTSC_PHCD)) + goto skip_phy_resume; + + writel(readl(USB_PORTSC) & ~PORTSC_PHCD, USB_PORTSC); + while (cnt < PHY_RESUME_TIMEOUT_USEC) { + if (!(readl(USB_PORTSC) & PORTSC_PHCD)) + break; + udelay(1); + cnt++; + } + + if (cnt >= PHY_RESUME_TIMEOUT_USEC) { + /* + * This is a fatal error. Reset the link and + * PHY. USB state can not be restored. Re-insertion + * of USB cable is the only way to get USB working. + */ + dev_err(otg->dev, "Unable to resume USB." + "Re-plugin the cable\n"); + msm_otg_reset(otg); + } + +skip_phy_resume: + if (device_may_wakeup(otg->dev)) + disable_irq_wake(motg->irq); + if (bus) + set_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags); + + if (motg->async_int) { + motg->async_int = 0; + pm_runtime_put(otg->dev); + enable_irq(motg->irq); + } + + atomic_set(&motg->in_lpm, 0); + + dev_info(otg->dev, "USB exited from low power mode\n"); + + return 0; +} + static void msm_otg_start_host(struct otg_transceiver *otg, int on) { struct msm_otg *motg = container_of(otg, struct msm_otg, otg); @@ -306,6 +455,7 @@ static int msm_otg_set_host(struct otg_transceiver *otg, struct usb_bus *host) if (!host) { if (otg->state == OTG_STATE_A_HOST) { + pm_runtime_get_sync(otg->dev); msm_otg_start_host(otg, 0); otg->host = NULL; otg->state = OTG_STATE_UNDEFINED; @@ -327,8 +477,10 @@ static int msm_otg_set_host(struct otg_transceiver *otg, struct usb_bus *host) * Kick the state machine work, if peripheral is not supported * or peripheral is already registered with us. */ - if (motg->pdata->mode == USB_HOST || otg->gadget) + if (motg->pdata->mode == USB_HOST || otg->gadget) { + pm_runtime_get_sync(otg->dev); schedule_work(&motg->sm_work); + } return 0; } @@ -376,6 +528,7 @@ static int msm_otg_set_peripheral(struct otg_transceiver *otg, if (!gadget) { if (otg->state == OTG_STATE_B_PERIPHERAL) { + pm_runtime_get_sync(otg->dev); msm_otg_start_peripheral(otg, 0); otg->gadget = NULL; otg->state = OTG_STATE_UNDEFINED; @@ -393,8 +546,10 @@ static int msm_otg_set_peripheral(struct otg_transceiver *otg, * Kick the state machine work, if host is not supported * or host is already registered with us. */ - if (motg->pdata->mode == USB_PERIPHERAL || otg->host) + if (motg->pdata->mode == USB_PERIPHERAL || otg->host) { + pm_runtime_get_sync(otg->dev); schedule_work(&motg->sm_work); + } return 0; } @@ -473,6 +628,7 @@ static void msm_otg_sm_work(struct work_struct *w) msm_otg_start_peripheral(otg, 1); otg->state = OTG_STATE_B_PERIPHERAL; } + pm_runtime_put_sync(otg->dev); break; case OTG_STATE_B_PERIPHERAL: dev_dbg(otg->dev, "OTG_STATE_B_PERIPHERAL state\n"); @@ -504,6 +660,13 @@ static irqreturn_t msm_otg_irq(int irq, void *data) struct otg_transceiver *otg = &motg->otg; u32 otgsc = 0; + if (atomic_read(&motg->in_lpm)) { + disable_irq_nosync(irq); + motg->async_int = 1; + pm_runtime_get(otg->dev); + return IRQ_HANDLED; + } + otgsc = readl(USB_OTGSC); if (!(otgsc & (OTGSC_IDIS | OTGSC_BSVIS))) return IRQ_NONE; @@ -514,12 +677,14 @@ static irqreturn_t msm_otg_irq(int irq, void *data) else clear_bit(ID, &motg->inputs); dev_dbg(otg->dev, "ID set/clear\n"); + pm_runtime_get_noresume(otg->dev); } else if ((otgsc & OTGSC_BSVIS) && (otgsc & OTGSC_BSVIE)) { if (otgsc & OTGSC_BSV) set_bit(B_SESS_VLD, &motg->inputs); else clear_bit(B_SESS_VLD, &motg->inputs); dev_dbg(otg->dev, "BSV set/clear\n"); + pm_runtime_get_noresume(otg->dev); } writel(otgsc, USB_OTGSC); @@ -616,6 +781,7 @@ static ssize_t msm_otg_mode_write(struct file *file, const char __user *ubuf, goto out; } + pm_runtime_get_sync(otg->dev); schedule_work(&motg->sm_work); out: return status; @@ -770,8 +936,10 @@ static int __init msm_otg_probe(struct platform_device *pdev) "not available\n"); } - return 0; + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + return 0; free_irq: free_irq(motg->irq, motg); disable_clks: @@ -796,23 +964,45 @@ static int __devexit msm_otg_remove(struct platform_device *pdev) { struct msm_otg *motg = platform_get_drvdata(pdev); struct otg_transceiver *otg = &motg->otg; + int cnt = 0; if (otg->host || otg->gadget) return -EBUSY; msm_otg_debugfs_cleanup(); cancel_work_sync(&motg->sm_work); + + msm_otg_resume(motg); + device_init_wakeup(&pdev->dev, 0); - otg_set_transceiver(NULL); + pm_runtime_disable(&pdev->dev); + otg_set_transceiver(NULL); free_irq(motg->irq, motg); + /* + * Put PHY in low power mode. + */ + ulpi_read(otg, 0x14); + ulpi_write(otg, 0x08, 0x09); + + writel(readl(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC); + while (cnt < PHY_SUSPEND_TIMEOUT_USEC) { + if (readl(USB_PORTSC) & PORTSC_PHCD) + break; + udelay(1); + cnt++; + } + if (cnt >= PHY_SUSPEND_TIMEOUT_USEC) + dev_err(otg->dev, "Unable to suspend PHY\n"); + clk_disable(motg->pclk); clk_disable(motg->clk); if (motg->core_clk) clk_disable(motg->core_clk); iounmap(motg->regs); + pm_runtime_set_suspended(&pdev->dev); clk_put(motg->phy_reset_clk); clk_put(motg->pclk); @@ -825,11 +1015,96 @@ static int __devexit msm_otg_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM_RUNTIME +static int msm_otg_runtime_idle(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + struct otg_transceiver *otg = &motg->otg; + + dev_dbg(dev, "OTG runtime idle\n"); + + /* + * It is observed some times that a spurious interrupt + * comes when PHY is put into LPM immediately after PHY reset. + * This 1 sec delay also prevents entering into LPM immediately + * after asynchronous interrupt. + */ + if (otg->state != OTG_STATE_UNDEFINED) + pm_schedule_suspend(dev, 1000); + + return -EAGAIN; +} + +static int msm_otg_runtime_suspend(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + + dev_dbg(dev, "OTG runtime suspend\n"); + return msm_otg_suspend(motg); +} + +static int msm_otg_runtime_resume(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + + dev_dbg(dev, "OTG runtime resume\n"); + return msm_otg_resume(motg); +} +#else +#define msm_otg_runtime_idle NULL +#define msm_otg_runtime_suspend NULL +#define msm_otg_runtime_resume NULL +#endif + +#ifdef CONFIG_PM +static int msm_otg_pm_suspend(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + + dev_dbg(dev, "OTG PM suspend\n"); + return msm_otg_suspend(motg); +} + +static int msm_otg_pm_resume(struct device *dev) +{ + struct msm_otg *motg = dev_get_drvdata(dev); + int ret; + + dev_dbg(dev, "OTG PM resume\n"); + + ret = msm_otg_resume(motg); + if (ret) + return ret; + + /* + * Runtime PM Documentation recommends bringing the + * device to full powered state upon resume. + */ + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} +#else +#define msm_otg_pm_suspend NULL +#define msm_otg_pm_resume NULL +#endif + +static const struct dev_pm_ops msm_otg_dev_pm_ops = { + .runtime_suspend = msm_otg_runtime_suspend, + .runtime_resume = msm_otg_runtime_resume, + .runtime_idle = msm_otg_runtime_idle, + .suspend = msm_otg_pm_suspend, + .resume = msm_otg_pm_resume, +}; + static struct platform_driver msm_otg_driver = { .remove = __devexit_p(msm_otg_remove), .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, + .pm = &msm_otg_dev_pm_ops, }, }; diff --git a/include/linux/usb/msm_hsusb.h b/include/linux/usb/msm_hsusb.h index b796df94ec72..3675e03b1539 100644 --- a/include/linux/usb/msm_hsusb.h +++ b/include/linux/usb/msm_hsusb.h @@ -88,6 +88,8 @@ struct msm_otg_platform_data { * @regs: ioremapped register base address. * @inputs: OTG state machine inputs(Id, SessValid etc). * @sm_work: OTG state machine work. + * @in_lpm: indicates low power mode (LPM) state. + * @async_int: Async interrupt arrived. * */ struct msm_otg { @@ -103,6 +105,8 @@ struct msm_otg { #define B_SESS_VLD 1 unsigned long inputs; struct work_struct sm_work; + atomic_t in_lpm; + int async_int; }; #endif diff --git a/include/linux/usb/msm_hsusb_hw.h b/include/linux/usb/msm_hsusb_hw.h index b061cffcf048..b92e17349c7b 100644 --- a/include/linux/usb/msm_hsusb_hw.h +++ b/include/linux/usb/msm_hsusb_hw.h @@ -44,6 +44,9 @@ #define ULPI_DATA(n) ((n) & 255) #define ULPI_DATA_READ(n) (((n) >> 8) & 255) +#define ASYNC_INTR_CTRL (1 << 29) /* Enable async interrupt */ +#define ULPI_STP_CTRL (1 << 30) /* Block communication with PHY */ + /* OTG definitions */ #define OTGSC_INTSTS_MASK (0x7f << 16) #define OTGSC_ID (1 << 8) -- cgit v1.2.3