From 2b9df57ebe5ff306fbbc5cabc0c8b82275dd245d Mon Sep 17 00:00:00 2001 From: Kyungmin Park Date: Wed, 28 Jan 2009 15:45:50 +0000 Subject: introduce-samsung-s3c64xx-usb-otg-driver.patch This was posted to the linux-usb list in Dec 2008 Signed-off-by: Minkyu Kang Signed-off-by: Kyungmin Park --- arch/arm/mach-s3c6400/include/mach/map.h | 6 + arch/arm/plat-s3c/include/plat/devs.h | 4 + arch/arm/plat-s3c/include/plat/map-base.h | 2 + arch/arm/plat-s3c/include/plat/regs-usb-hs-otg.h | 360 +++++ arch/arm/plat-s3c64xx/dev-usbgadget.c | 32 + drivers/usb/gadget/Kconfig | 16 + drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/epautoconf.c | 12 + drivers/usb/gadget/gadget_chips.h | 8 + drivers/usb/gadget/s3c-udc.h | 131 ++ drivers/usb/gadget/s3c_hs_otg.c | 1854 ++++++++++++++++++++++ 11 files changed, 2426 insertions(+) create mode 100644 arch/arm/plat-s3c/include/plat/regs-usb-hs-otg.h create mode 100644 arch/arm/plat-s3c64xx/dev-usbgadget.c create mode 100644 drivers/usb/gadget/s3c-udc.h create mode 100644 drivers/usb/gadget/s3c_hs_otg.c diff --git a/arch/arm/mach-s3c6400/include/mach/map.h b/arch/arm/mach-s3c6400/include/mach/map.h index baf1c0f1ea5..19d4684f2ed 100644 --- a/arch/arm/mach-s3c6400/include/mach/map.h +++ b/arch/arm/mach-s3c6400/include/mach/map.h @@ -68,4 +68,10 @@ #define S3C_PA_IIC1 S3C64XX_PA_IIC1 #define S3C_PA_FB S3C64XX_PA_FB +#define S3C64XX_VA_OTG S3C_VA_OTG +#define S3C64XX_PA_OTG (0x7C000000) + +#define S3C64XX_VA_OTGSFR S3C_VA_OTGSFR +#define S3C64XX_PA_OTGSFR (0x7C100000) + #endif /* __ASM_ARCH_6400_MAP_H */ diff --git a/arch/arm/plat-s3c/include/plat/devs.h b/arch/arm/plat-s3c/include/plat/devs.h index cf160ab4752..ed6965c591f 100644 --- a/arch/arm/plat-s3c/include/plat/devs.h +++ b/arch/arm/plat-s3c/include/plat/devs.h @@ -16,6 +16,10 @@ struct s3c24xx_uart_resources { unsigned long nr_resources; }; +struct s3c_plat_otg_data { + int phyclk; +}; + extern struct s3c24xx_uart_resources s3c2410_uart_resources[]; extern struct s3c24xx_uart_resources s3c64xx_uart_resources[]; diff --git a/arch/arm/plat-s3c/include/plat/map-base.h b/arch/arm/plat-s3c/include/plat/map-base.h index b84289d32a5..0bb9c168bbb 100644 --- a/arch/arm/plat-s3c/include/plat/map-base.h +++ b/arch/arm/plat-s3c/include/plat/map-base.h @@ -36,5 +36,7 @@ #define S3C_VA_TIMER S3C_ADDR(0x00300000) /* timer block */ #define S3C_VA_WATCHDOG S3C_ADDR(0x00400000) /* watchdog */ #define S3C_VA_UART S3C_ADDR(0x01000000) /* UART */ +#define S3C_VA_OTG S3C_ADDR(0x03900000) /* OTG */ +#define S3C_VA_OTGSFR S3C_ADDR(0x03a00000) /* OTGSFR */ #endif /* __ASM_PLAT_MAP_H */ diff --git a/arch/arm/plat-s3c/include/plat/regs-usb-hs-otg.h b/arch/arm/plat-s3c/include/plat/regs-usb-hs-otg.h new file mode 100644 index 00000000000..fa1a0060758 --- /dev/null +++ b/arch/arm/plat-s3c/include/plat/regs-usb-hs-otg.h @@ -0,0 +1,360 @@ +/* linux/include/asm-arm/arch-s3c2410/regs-udc.h + * + * Copyright (C) 2008 Samsung Electronics + * Copyright (C) 2004 Herbert Poetzl + * + * This include file is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. +*/ + +#ifndef __ASM_ARCH_REGS_USB_HS_OTG_H +#define __ASM_ARCH_REGS_USB_HS_OTG_H + +/* USB2.0 OTG Controller register */ +#define S3C_USBOTG_PHYREG(x) ((x) + S3C64XX_VA_OTGSFR) +#define S3C_USBOTG_PHYPWR S3C_USBOTG_PHYREG(0x0) +#define S3C_USBOTG_PHYCLK S3C_USBOTG_PHYREG(0x4) +#define S3C_USBOTG_RSTCON S3C_USBOTG_PHYREG(0x8) + +/* USB2.0 OTG Controller register */ +/* Core Global Registers */ +#define S3C_USBOTGREG(x) ((x) + S3C64XX_VA_OTG) +/* OTG Control & Status */ +#define S3C_UDC_OTG_GOTGCTL S3C_USBOTGREG(0x000) +/* OTG Interrupt */ +#define S3C_UDC_OTG_GOTGINT S3C_USBOTGREG(0x004) +/* Core AHB Configuration */ +#define S3C_UDC_OTG_GAHBCFG S3C_USBOTGREG(0x008) +/* Core USB Configuration */ +#define S3C_UDC_OTG_GUSBCFG S3C_USBOTGREG(0x00C) +/* Core Reset */ +#define S3C_UDC_OTG_GRSTCTL S3C_USBOTGREG(0x010) +/* Core Interrupt */ +#define S3C_UDC_OTG_GINTSTS S3C_USBOTGREG(0x014) +/* Core Interrupt Mask */ +#define S3C_UDC_OTG_GINTMSK S3C_USBOTGREG(0x018) +/* Receive Status Debug Read/Status Read */ +#define S3C_UDC_OTG_GRXSTSR S3C_USBOTGREG(0x01C) +/* Receive Status Debug Pop/Status Pop */ +#define S3C_UDC_OTG_GRXSTSP S3C_USBOTGREG(0x020) +/* Receive FIFO Size */ +#define S3C_UDC_OTG_GRXFSIZ S3C_USBOTGREG(0x024) +/* Non-Periodic Transmit FIFO Size */ +#define S3C_UDC_OTG_GNPTXFSIZ S3C_USBOTGREG(0x028) +/* Non-Periodic Transmit FIFO/Queue Status */ +#define S3C_UDC_OTG_GNPTXSTS S3C_USBOTGREG(0x02C) + +/* Host Periodic Transmit FIFO Size */ +#define S3C_UDC_OTG_HPTXFSIZ S3C_USBOTGREG(0x100) +/* Device Periodic Transmit FIFO-1 Size */ +#define S3C_UDC_OTG_DPTXFSIZ1 S3C_USBOTGREG(0x104) +/* Device Periodic Transmit FIFO-2 Size */ +#define S3C_UDC_OTG_DPTXFSIZ2 S3C_USBOTGREG(0x108) +/* Device Periodic Transmit FIFO-3 Size */ +#define S3C_UDC_OTG_DPTXFSIZ3 S3C_USBOTGREG(0x10C) +/* Device Periodic Transmit FIFO-4 Size */ +#define S3C_UDC_OTG_DPTXFSIZ4 S3C_USBOTGREG(0x110) +/* Device Periodic Transmit FIFO-5 Size */ +#define S3C_UDC_OTG_DPTXFSIZ5 S3C_USBOTGREG(0x114) +/* Device Periodic Transmit FIFO-6 Size */ +#define S3C_UDC_OTG_DPTXFSIZ6 S3C_USBOTGREG(0x118) +/* Device Periodic Transmit FIFO-7 Size */ +#define S3C_UDC_OTG_DPTXFSIZ7 S3C_USBOTGREG(0x11C) +/* Device Periodic Transmit FIFO-8 Size */ +#define S3C_UDC_OTG_DPTXFSIZ8 S3C_USBOTGREG(0x120) +/* Device Periodic Transmit FIFO-9 Size */ +#define S3C_UDC_OTG_DPTXFSIZ9 S3C_USBOTGREG(0x124) +/* Device Periodic Transmit FIFO-10 Size */ +#define S3C_UDC_OTG_DPTXFSIZ10 S3C_USBOTGREG(0x128) +/* Device Periodic Transmit FIFO-11 Size */ +#define S3C_UDC_OTG_DPTXFSIZ11 S3C_USBOTGREG(0x12C) +/* Device Periodic Transmit FIFO-12 Size */ +#define S3C_UDC_OTG_DPTXFSIZ12 S3C_USBOTGREG(0x130) +/* Device Periodic Transmit FIFO-13 Size */ +#define S3C_UDC_OTG_DPTXFSIZ13 S3C_USBOTGREG(0x134) +/* Device Periodic Transmit FIFO-14 Size */ +#define S3C_UDC_OTG_DPTXFSIZ14 S3C_USBOTGREG(0x138) +/* Device Periodic Transmit FIFO-15 Size */ +#define S3C_UDC_OTG_DPTXFSIZ15 S3C_USBOTGREG(0x13C) + +/* Host Mode Registers + * Host Global Registers */ +/* Host Configuration */ +#define S3C_UDC_OTG_HCFG S3C_USBOTGREG(0x400) +/* Host Frame Interval */ +#define S3C_UDC_OTG_HFIR S3C_USBOTGREG(0x404) +/* Host Frame Number/Frame Time Remaining */ +#define S3C_UDC_OTG_HFNUM S3C_USBOTGREG(0x408) +/* Host Periodic Transmit FIFO/Queue Status */ +#define S3C_UDC_OTG_HPTXSTS S3C_USBOTGREG(0x410) +/* Host All Channels Interrupt */ +#define S3C_UDC_OTG_HAINT S3C_USBOTGREG(0x414) +/* Host All Channels Interrupt Mask */ +#define S3C_UDC_OTG_HAINTMSK S3C_USBOTGREG(0x418) + +/* Host Port Control & Status Registers */ +#define S3C_UDC_OTG_HPRT S3C_USBOTGREG(0x440) + +/* Host Channel-Specific Registers */ +/* Host Channel-0 Characteristics */ +#define S3C_UDC_OTG_HCCHAR0 S3C_USBOTGREG(0x500) +/* Host Channel-0 Split Control */ +#define S3C_UDC_OTG_HCSPLT0 S3C_USBOTGREG(0x504) +/* Host Channel-0 Interrupt */ +#define S3C_UDC_OTG_HCINT0 S3C_USBOTGREG(0x508) +/* Host Channel-0 Interrupt Mask */ +#define S3C_UDC_OTG_HCINTMSK0 S3C_USBOTGREG(0x50C) +/* Host Channel-0 Transfer Size */ +#define S3C_UDC_OTG_HCTSIZ0 S3C_USBOTGREG(0x510) +/* Host Channel-0 DMA Address */ +#define S3C_UDC_OTG_HCDMA0 S3C_USBOTGREG(0x514) + +/* Device Mode Registers + * Device Global Registers */ +/* Device Configuration */ +#define S3C_UDC_OTG_DCFG S3C_USBOTGREG(0x800) +/* Device Control */ +#define S3C_UDC_OTG_DCTL S3C_USBOTGREG(0x804) +/* Device Status */ +#define S3C_UDC_OTG_DSTS S3C_USBOTGREG(0x808) +/* Device IN Endpoint Common Interrupt Mask */ +#define S3C_UDC_OTG_DIEPMSK S3C_USBOTGREG(0x810) +/* Device OUT Endpoint Common Interrupt Mask */ +#define S3C_UDC_OTG_DOEPMSK S3C_USBOTGREG(0x814) +/* Device All Endpoints Interrupt */ +#define S3C_UDC_OTG_DAINT S3C_USBOTGREG(0x818) +/* Device All Endpoints Interrupt Mask */ +#define S3C_UDC_OTG_DAINTMSK S3C_USBOTGREG(0x81C) +/* Device IN Token Sequence Learning Queue Read 1 */ +#define S3C_UDC_OTG_DTKNQR1 S3C_USBOTGREG(0x820) +/* Device IN Token Sequence Learning Queue Read 2 */ +#define S3C_UDC_OTG_DTKNQR2 S3C_USBOTGREG(0x824) +/* Device VBUS Discharge Time */ +#define S3C_UDC_OTG_DVBUSDIS S3C_USBOTGREG(0x828) +/* Device VBUS Pulsing Time */ +#define S3C_UDC_OTG_DVBUSPULSE S3C_USBOTGREG(0x82C) +/* Device IN Token Sequence Learning Queue Read 3 */ +#define S3C_UDC_OTG_DTKNQR3 S3C_USBOTGREG(0x830) +/* Device IN Token Sequence Learning Queue Read 4 */ +#define S3C_UDC_OTG_DTKNQR4 S3C_USBOTGREG(0x834) + +/* Device Logical IN Endpoint-Specific Registers */ +/* Device IN Endpoint 0 Control */ +#define S3C_UDC_OTG_DIEPCTL0 S3C_USBOTGREG(0x900) +/* Device IN Endpoint 0 Interrupt */ +#define S3C_UDC_OTG_DIEPINT0 S3C_USBOTGREG(0x908) +/* Device IN Endpoint 0 Transfer Size */ +#define S3C_UDC_OTG_DIEPTSIZ0 S3C_USBOTGREG(0x910) +/* Device IN Endpoint 0 DMA Address */ +#define S3C_UDC_OTG_DIEPDMA0 S3C_USBOTGREG(0x914) + +/* Device IN Endpoint 2 Control */ +#define S3C_UDC_OTG_DIEPCTL2 S3C_USBOTGREG(0x940) +/* Device IN Endpoint 2 Interrupt */ +#define S3C_UDC_OTG_DIEPINT2 S3C_USBOTGREG(0x948) +/* Device IN Endpoint 2 Transfer Size */ +#define S3C_UDC_OTG_DIEPTSIZ2 S3C_USBOTGREG(0x950) +/* Device IN Endpoint 2 DMA Address */ +#define S3C_UDC_OTG_DIEPDMA2 S3C_USBOTGREG(0x954) + +/* Device IN Endpoint 3 Control */ +#define S3C_UDC_OTG_DIEPCTL3 S3C_USBOTGREG(0x960) +/* Device IN Endpoint 3 Interrupt */ +#define S3C_UDC_OTG_DIEPINT3 S3C_USBOTGREG(0x968) +/* Device IN Endpoint 3 Transfer Size */ +#define S3C_UDC_OTG_DIEPTSIZ3 S3C_USBOTGREG(0x970) +/* Device IN Endpoint 3 DMA Address */ +#define S3C_UDC_OTG_DIEPDMA3 S3C_USBOTGREG(0x974) + +/* Device Logical OUT Endpoint-Specific Registers */ +/* Device OUT Endpoint 0 Control */ +#define S3C_UDC_OTG_DOEPCTL0 S3C_USBOTGREG(0xB00) +/* Device OUT Endpoint 0 Interrupt */ +#define S3C_UDC_OTG_DOEPINT0 S3C_USBOTGREG(0xB08) +/* Device OUT Endpoint 0 Transfer Size */ +#define S3C_UDC_OTG_DOEPTSIZ0 S3C_USBOTGREG(0xB10) +/* Device OUT Endpoint 0 DMA Address */ +#define S3C_UDC_OTG_DOEPDMA0 S3C_USBOTGREG(0xB14) + +/* Device OUT Endpoint 1 Control */ +#define S3C_UDC_OTG_DOEPCTL1 S3C_USBOTGREG(0xB20) +/* Device OUT Endpoint 1 Interrupt */ +#define S3C_UDC_OTG_DOEPINT1 S3C_USBOTGREG(0xB28) +/* Device OUT Endpoint 1 Transfer Size */ +#define S3C_UDC_OTG_DOEPTSIZ1 S3C_USBOTGREG(0xB30) +/* Device OUT Endpoint 1 DMA Address */ +#define S3C_UDC_OTG_DOEPDMA1 S3C_USBOTGREG(0xB34) + +/* Endpoint FIFO address */ +#define S3C_UDC_OTG_EP0_FIFO S3C_USBOTGREG(0x1000) +#define S3C_UDC_OTG_EP1_FIFO S3C_USBOTGREG(0x2000) +#define S3C_UDC_OTG_EP2_FIFO S3C_USBOTGREG(0x3000) +#define S3C_UDC_OTG_EP3_FIFO S3C_USBOTGREG(0x4000) +#define S3C_UDC_OTG_EP4_FIFO S3C_USBOTGREG(0x5000) +#define S3C_UDC_OTG_EP5_FIFO S3C_USBOTGREG(0x6000) +#define S3C_UDC_OTG_EP6_FIFO S3C_USBOTGREG(0x7000) +#define S3C_UDC_OTG_EP7_FIFO S3C_USBOTGREG(0x8000) +#define S3C_UDC_OTG_EP8_FIFO S3C_USBOTGREG(0x9000) + +/* S3C_USBOTG_PHYPWR */ +#define OTG_ENABLE (0x0<<4) +#define OTG_DISABLE (0x1<<4) +#define ANALOG_PWR_UP (0x0<<3) +#define ANALOG_PWR_DOWN (0x1<<3) +#define SUSPEND_DISABLE (0x0<<0) +#define SUSPEND_ENABLE (0x1<<0) + +/* S3C_USBOTG_PHYCLK */ +#define REF_CLK_CRYSTAL (0x0<<5) +#define REF_CLK_OSCC (0x1<<5) + +/* S3C_USBOTG_RSTCON */ +#define SW_RST_OFF (0x0<<0) +#define SW_RST_ON (0x1<<0) + +/* S3C_UDC_OTG_GOTGCTL */ +#define B_SESSION_VALID (0x1<<19) +#define A_SESSION_VALID (0x1<<18) + +/* S3C_UDC_OTG_GAHBCFG */ +#define PTXFE_HALF (0x0<<8) +#define PTXFE_ZERO (0x1<<8) +#define NPTXFE_HALF (0x0<<7) +#define NPTXFE_ZERO (0x1<<7) +#define MODE_SLAVE (0x0<<5) +#define MODE_DMA (0x1<<5) +#define BURST_SINGLE (0x0<<1) +#define BURST_INCR (0x1<<1) +#define BURST_INCR4 (0x3<<1) +#define BURST_INCR8 (0x5<<1) +#define BURST_INCR16 (0x7<<1) +#define GBL_INT_UNMASK (0x1<<0) +#define GBL_INT_MASK (0x0<<0) + +/* S3C_UDC_OTG_GUSBCFG */ +#define PHY_CLK_480M (0x0<<15) +#define PHY_CLK_48M (0x1<<15) +#define TXFIFO_RE_DIS (0x0<<14) +#define TXFIFO_RE_EN (0x1<<14) +#define TURN_AROUND (0x5<<10) +#define HNP_DISABLE (0x0<<9) +#define HNP_ENABLE (0x1<<9) +#define SRP_DISABLE (0x0<<8) +#define SRP_ENABLE (0x1<<8) +#define ULPI_DDR (0x0<<7) +#define HS_UTMI (0x0<<6) +#define INTERF_UTMI (0x0<<4) +#define INTERF_ULPI (0x1<<4) +#define PHY_INTERF_8 (0x0<<3) +#define PHY_INTERF_16 (0x1<<3) +#define TIME_OUT_CAL (0x7<<0) + +/* S3C_UDC_OTG_GRSTCTL */ +#define AHB_MASTER_IDLE (1u<<31) +#define CORE_SOFT_RESET (0x1<<0) + +/* S3C_UDC_OTG_GINTSTS/S3C_UDC_OTG_GINTMSK core interrupt register */ +#define INT_RESUME (0x1<<31) +#define INT_DISCONN (0x1<<29) +#define INT_CONN_CNG (0x1<<28) +#define INT_OUT_EP (0x1<<19) +#define INT_IN_EP (0x1<<18) +#define INT_ENUMDONE (0x1<<13) +#define INT_RESET (0x1<<12) +#define INT_SUSPEND (0x1<<11) +#define INT_EARLY_SUSPEND (0x1<<10) +#define INT_TX_FIFO_EMPTY (0x1<<5) +#define INT_RX_FIFO_NOT_EMPTY (0x1<<4) +#define INT_SOF (0x1<<3) +#define INT_DEV_MODE (0x0<<0) +#define INT_HOST_MODE (0x1<<1) + +#define FULL_SPEED_CONTROL_PKT_SIZE 8 +#define FULL_SPEED_BULK_PKT_SIZE 64 + +#define HIGH_SPEED_CONTROL_PKT_SIZE 64 +#define HIGH_SPEED_BULK_PKT_SIZE 512 + +/* S3C_UDC_OTG_DSTS */ +#define RX_FIFO_SIZE (2048<<0) +#define NPTX_FIFO_START_ADDR (RX_FIFO_SIZE<<0) +#define NPTX_FIFO_SIZE (2048<<16) +#define PTX_FIFO_SIZE (2048<<16) +#define USB_HIGH_30_60MHZ (0x0<<1) +#define USB_FULL_30_60MHZ (0x1<<1) +#define USB_LOW_6MHZ (0x2<<1) +#define USB_FULL_48MHZ (0x3<<1) + +/* S3C_UDC_OTG_GRXSTSP */ +#define BYTE_COUNT(x) ((x & (0x7FF<<4)) >> 4) +#define PKT_STS(x) ((x & (0xF<<17)) >> 17) +#define EP_NUM(x) (x & 0xF) + +#define OUT_PKT_RECEIVED (0x2<<17) +#define OUT_COMPLELTED (0x3<<17) +#define SETUP_COMPLETED (0x4<<17) +#define SETUP_PKT_RECEIVED (0x6<<17) + +/* S3C_UDC_OTG_DCFG */ +#define EP_MIS_CNT(x) (x<<18) +#define DEVICE_ADDR(x) (x<<4) +#define SPEED_2_HIGH (0x0<<0) +#define SPEED_2_FULL (0x1<<0) +#define SPEED_1_LOW (0x2<<0) +#define SPEED_1_FULL (0x3<<0) + +/* S3C_UDC_OTG_DCTL device control register */ +#define NORMAL_OPERATION (0x1<<0) +#define SOFT_DISCONNECT (0x1<<1) + +/* S3C_UDC_OTG_DSTS */ +#define ENUM_SPEED(x) (x & (0x3<<1)) +#define FRAME_CNT(x) (x & (0x3ff<<8)) + +/* S3C_UDC_OTG_DAINT device all endpoint interrupt register */ +#define S3C_UDC_INT_IN_EP0 (0x1<<0) +#define S3C_UDC_INT_IN_EP2 (0x1<<2) +#define S3C_UDC_INT_IN_EP3 (0x1<<3) +#define S3C_UDC_INT_OUT_EP0 (0x1<<16) +#define S3C_UDC_INT_OUT_EP1 (0x1<<17) +#define S3C_UDC_INT_OUT_EP4 (0x1<<20) + +/* S3C_UDC_OTG_DIEPCTL0/DOEPCTL0 device control + IN/OUT endpoint 0 control register */ +#define DEPCTL_EPENA (0x1<<31) +#define DEPCTL_EPDIS (0x1<<30) +#define DEPCTL_SNAK (0x1<<27) +#define DEPCTL_CNAK (0x1<<26) +#define DEPCTL_CTRL_TYPE (0x0<<18) +#define DEPCTL_ISO_TYPE (0x1<<18) +#define DEPCTL_BULK_TYPE (0x2<<18) +#define DEPCTL_INTR_TYPE (0x3<<18) +#define DEPCTL_USBACTEP (0x1<<15) +#define DEPCTL0_MPS_64 (0x0<<0) +#define DEPCTL0_MPS_32 (0x1<<0) +#define DEPCTL0_MPS_16 (0x2<<0) +#define DEPCTL0_MPS_8 (0x3<<0) + +/* S3C_UDC_OTG_DIEPINTn */ +#define IN_EP_NAK_EFF (0x1<<6) +#define IN_TK_EPMIS (0x1<<5) +#define IN_TK_TXFEMP (0x1<<4) +#define IN_EP_TIMEOUT (0x1<<3) + +/* S3C_UDC_OTG_DOEPINTn */ +#define BACK2BACK_SETUP (0x1<<6) +#define OUT_TK_EP_DIS (0x1<<4) +#define SETUP_PHASE_DONE (0x1<<3) + +/* S3C_UDC_OTG_DIEPINTn/DOEPINTn */ +#define AHB_ERROR (0x1<<2) +#define EPDISBLD (0x1<<1) +#define TRANSFER_DONE (0x1<<0) + +/* S3C_UDC_OTG_DIEPTSIZn */ +#define PKT_CNT(x) (x<<19) +#define XFERSIZE(x) (x<<0) + +#endif diff --git a/arch/arm/plat-s3c64xx/dev-usbgadget.c b/arch/arm/plat-s3c64xx/dev-usbgadget.c new file mode 100644 index 00000000000..17d33f0b7e2 --- /dev/null +++ b/arch/arm/plat-s3c64xx/dev-usbgadget.c @@ -0,0 +1,32 @@ +/* Base S3C64XX usbgadget resource and device definitions */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +static struct resource s3c_usbgadget_resource[] = { + [0] = { + .start = S3C64XX_PA_OTG, + .end = S3C64XX_PA_OTG + SZ_1M - 1, + .flags = IORESOURCE_MEM, + }, + [1] = { + .start = IRQ_OTG, + .end = IRQ_OTG, + .flags = IORESOURCE_IRQ, + } +}; + +struct platform_device s3c_device_usbgadget = { + .name = "s3c-otg-usbgadget", + .id = -1, + .num_resources = ARRAY_SIZE(s3c_usbgadget_resource), + .resource = s3c_usbgadget_resource, +}; +EXPORT_SYMBOL(s3c_device_usbgadget); diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 3219d137340..5c7a710ad38 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -291,6 +291,22 @@ config USB_S3C2410_DEBUG boolean "S3C2410 udc debug messages" depends on USB_GADGET_S3C2410 +config USB_GADGET_S3C_OTGD_HS + boolean "S3C high speed(2.0, dual-speed) USB OTG device" + depends on (CPU_S3C6400 || CPU_S3C6410) + select USB_GADGET_SELECTED + select USB_GADGET_DUALSPEED + help + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "s3c_udc_hs" and force all + gadget drivers to also be dynamically linked. + +config USB_S3C + tristate + depends on USB_GADGET_S3C_FS && USB_GADGET_S3C_HS && USB_GADGET_S3C_OTGD_HS + default USB_GADGET + select USB_GADGET_SELECTED + # # Controllers available in both integrated and discrete versions # diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 39a51d746cb..d4f746a55e4 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_USB_AT91) += at91_udc.o obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o obj-$(CONFIG_USB_M66592) += m66592-udc.o +obj-$(CONFIG_USB_GADGET_S3C_OTGD_HS) += s3c_hs_otg.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_CI13XXX) += ci13xxx_udc.o diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c index a36b1175b18..a88f59a00cd 100644 --- a/drivers/usb/gadget/epautoconf.c +++ b/drivers/usb/gadget/epautoconf.c @@ -275,6 +275,18 @@ struct usb_ep * __init usb_ep_autoconfig ( ep = find_ep (gadget, "ep1-bulk"); if (ep && ep_matches (gadget, ep, desc)) return ep; + } else if (gadget_is_s3c64xx(gadget)) { + if (USB_ENDPOINT_XFER_INT == type) { + /* single buffering is enough */ + ep = find_ep(gadget, "ep3-int"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; + } else if (USB_ENDPOINT_XFER_BULK == type + && (USB_DIR_IN & desc->bEndpointAddress)) { + ep = find_ep(gadget, "ep2-bulk"); + if (ep && ep_matches(gadget, ep, desc)) + return ep; + } } /* Second, look at endpoints until an unclaimed one looks usable */ diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index ec6d439a2aa..7f9330578f9 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -104,6 +104,12 @@ #define gadget_is_s3c2410(g) 0 #endif +#ifdef CONFIG_USB_GADGET_S3C_OTGD_HS +#define gadget_is_s3c64xx(g) !strcmp("s3c-otg-device", (g)->name) +#else +#define gadget_is_s3c64xx(g) 0 +#endif + #ifdef CONFIG_USB_GADGET_AT91 #define gadget_is_at91(g) !strcmp("at91_udc", (g)->name) #else @@ -231,6 +237,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x22; else if (gadget_is_ci13xxx(gadget)) return 0x23; + else if (gadget_is_s3c64xx(gadget)) + return 0x24; return -ENOENT; } diff --git a/drivers/usb/gadget/s3c-udc.h b/drivers/usb/gadget/s3c-udc.h new file mode 100644 index 00000000000..2eda34d3d12 --- /dev/null +++ b/drivers/usb/gadget/s3c-udc.h @@ -0,0 +1,131 @@ +/* + * drivers/usb/gadget/s3c-udc.h + * Samsung S3C on-chip full/high speed USB device controllers + * + * Copyright (C) 2008 Samsung Electronics + * Minkyu Kang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __S3C_UDC_H +#define __S3C_UDC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Max packet size */ +#if defined(CONFIG_USB_GADGET_S3C_FS) +#define EP0_FIFO_SIZE 8 +#define EP_FIFO_SIZE 64 +#define S3C_MAX_ENDPOINTS 5 +#elif defined(CONFIG_USB_GADGET_S3C_HS) +#define EP0_FIFO_SIZE 64 +#define EP_FIFO_SIZE 512 +#define EP_FIFO_SIZE2 1024 +#define S3C_MAX_ENDPOINTS 9 +#else +#define EP0_FIFO_SIZE 64 +#define EP_FIFO_SIZE 512 +#define EP_FIFO_SIZE2 1024 +#define S3C_MAX_ENDPOINTS 16 +#endif + +#define WAIT_FOR_SETUP 0 +#define DATA_STATE_XMIT 1 +#define DATA_STATE_NEED_ZLP 2 +#define WAIT_FOR_OUT_STATUS 3 +#define DATA_STATE_RECV 4 + +enum ep_type { + ep_control, ep_bulk_in, ep_bulk_out, ep_interrupt +}; + +struct s3c_ep { + struct usb_ep ep; + struct s3c_udc *dev; + + const struct usb_endpoint_descriptor *desc; + struct list_head queue; + unsigned long pio_irqs; + + u8 stopped; + u8 bEndpointAddress; + u8 bmAttributes; + + u32 ep_type; + u32 fifo; +#ifdef CONFIG_USB_GADGET_S3C_FS + u32 csr1; + u32 csr2; +#endif +}; + +struct s3c_request { + struct usb_request req; + struct list_head queue; +}; + +struct s3c_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct platform_device *dev; + spinlock_t lock; + + int phyclk; + int ep0state; + struct s3c_ep ep[S3C_MAX_ENDPOINTS]; + + unsigned char usb_address; + + unsigned req_pending:1; + unsigned req_std:1; + unsigned req_config:1; +}; + +extern struct s3c_udc *the_controller; + +#define ep_is_in(EP) (((EP)->bEndpointAddress & USB_DIR_IN) \ + == USB_DIR_IN) +#define ep_index(EP) ((EP)->bEndpointAddress & 0xF) +#define ep_maxpacket(EP) ((EP)->ep.maxpacket) + +#endif diff --git a/drivers/usb/gadget/s3c_hs_otg.c b/drivers/usb/gadget/s3c_hs_otg.c new file mode 100644 index 00000000000..aa3fd2d36d8 --- /dev/null +++ b/drivers/usb/gadget/s3c_hs_otg.c @@ -0,0 +1,1854 @@ +/* + * drivers/usb/gadget/s3c_hs_otg.c + * Samsung S3C on-chip full/high speed USB OTG 2.0 device controllers + * + * Copyright (C) 2008 Samsung Electronics + * Minkyu Kang + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "s3c-udc.h" +#include +#include +#include +#include +#include +#include +#include + +static char *state_names[] = { + "WAIT_FOR_SETUP", + "DATA_STATE_XMIT", + "DATA_STATE_NEED_ZLP", + "WAIT_FOR_OUT_STATUS", + "DATA_STATE_RECV" +}; + +#define S3C_USB_DBG_LEVEL 3 + +#define DBG(level, fmt, args...) do { \ + if (level >= S3C_USB_DBG_LEVEL) { \ + printk(KERN_INFO "[%s] " fmt, \ + __func__, ##args); \ + } } while (0) + + +#define DRIVER_DESC "Samsung Dual-speed USB 2.0 OTG Device Controller" +#define DRIVER_AUTHOR "Samsung Electronics" +#define DRIVER_VERSION "04 Dec 2008" + + +struct s3c_udc *the_controller; + +static const char driver_name[] = "s3c-otg-device"; +static const char driver_desc[] = DRIVER_DESC; +static const char ep0name[] = "ep0-control"; + +static u32 tx_ep_num = 2; + +static u32 ep0_fifo_size = EP0_FIFO_SIZE; +static u32 ep_fifo_size = EP_FIFO_SIZE; +static u32 ep_fifo_size2 = EP_FIFO_SIZE2; + +struct usb_ctrlrequest ctrl; +static int reset_available = 1; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + +static const char proc_node_name[] = "driver/otg"; + +static int +udc_proc_read(char *page, char **start, off_t off, int count, + int *eof, void *_dev) +{ + char *buf = page; + struct s3c_udc *dev = _dev; + char *next = buf; + unsigned size = count; + unsigned long flags; + int t; + + if (off != 0) + return 0; + + local_irq_save(flags); + + /* basic device status */ + t = scnprintf(next, size, + DRIVER_DESC "\n" + "%s version: %s\n" + "Gadget driver: %s\n" + "\n", + driver_name, DRIVER_VERSION, + dev->driver ? dev->driver->driver.name : "(none)"); + size -= t; + next += t; + + local_irq_restore(flags); + *eof = 1; + return count - size; +} + +#define create_proc_files() \ + create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev) +#define remove_proc_files() \ + remove_proc_entry(proc_node_name, NULL) + +#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ + +#define create_proc_files() do {} while (0) +#define remove_proc_files() do {} while (0) + +#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ + + +static inline u32 s3c_otg_readl(u32 reg) +{ + return readl(reg); +} + +static inline void s3c_otg_writel(u32 val, u32 reg, int update) +{ + u32 temp = 0; + + if (update) + temp = readl(reg); + + writel(val|temp, reg); +} + +/* + * retire a request + */ +static void s3c_otg_done(struct s3c_ep *ep, struct s3c_request *req, int status) +{ + unsigned int stopped = ep->stopped; + + DBG(1, "%s %p, stopped = %d\n", ep->ep.name, ep, stopped); + list_del_init(&req->queue); + + if (req->req.status == -EINPROGRESS) + req->req.status = status; + else + status = req->req.status; + + if (status && (status != -ESHUTDOWN)) + DBG(2, "complete %s stat %d len %u/%u\n", + ep->ep.name, status, req->req.actual, req->req.length); + + /* don't modify queue heads during completion callback */ + ep->stopped = 1; + + spin_unlock(&ep->dev->lock); + req->req.complete(&ep->ep, &req->req); + spin_lock(&ep->dev->lock); + + ep->stopped = stopped; +} + +/* + * dequeue ALL requests + */ +void s3c_otg_nuke(struct s3c_ep *ep, int status) +{ + struct s3c_request *req; + + DBG(1, "%s %p\n", ep->ep.name, ep); + + /* called with irqs blocked */ + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + s3c_otg_done(ep, req, status); + } +} + +static void s3c_otg_ep_control(int ep, int dir, u32 val, int update) +{ + u32 epctrl; + + switch (ep) { + case 0: + if (dir) + epctrl = (u32)S3C_UDC_OTG_DIEPCTL0; + else + epctrl = (u32)S3C_UDC_OTG_DOEPCTL0; + break; + case 1: + if (dir) + epctrl = -EOPNOTSUPP; + else + epctrl = (u32)S3C_UDC_OTG_DOEPCTL1; + break; + case 2: + if (dir) + epctrl = (u32)S3C_UDC_OTG_DIEPCTL2; + else + epctrl = -EOPNOTSUPP; + break; + case 3: + if (dir) + epctrl = (u32)S3C_UDC_OTG_DIEPCTL3; + else + epctrl = -EOPNOTSUPP; + break; + default: + DBG(3, "ep%d is unused Endpoint", ep); + return; + } + + if (epctrl < 0) { + DBG(3, "ep%d - %s is invalid direction\n", + ep, dir ? "IN" : "OUT"); + return; + } + + s3c_otg_writel(val, epctrl, update); +} + +static int s3c_otg_write_packet(struct s3c_ep *ep, + struct s3c_request *req, int max) +{ + u32 *buf; + int length; + int count; + u32 fifo = ep->fifo; + u32 epsize; + + buf = req->req.buf + req->req.actual; + prefetch(buf); + + length = req->req.length - req->req.actual; + length = min(length, max); + req->req.actual += length; + + DBG(1, "%s: %d/%d, fifo=0x%x\n", ep->ep.name, length, max, fifo); + + switch (ep_index(ep)) { + case 0: + epsize = (u32)S3C_UDC_OTG_DIEPTSIZ0; + break; + case 2: + epsize = (u32)S3C_UDC_OTG_DIEPTSIZ2; + break; + case 3: + epsize = (u32)S3C_UDC_OTG_DIEPTSIZ3; + break; + default: + DBG(3, "ep%d is unused Endpoint", ep_index(ep)); + return 0; + } + + s3c_otg_writel(PKT_CNT(0x1)|XFERSIZE(length), epsize, 0); + s3c_otg_ep_control(ep_index(ep), USB_DIR_IN, + DEPCTL_EPENA|DEPCTL_CNAK, 1); + + for (count = 0; count < length; count += 4) + s3c_otg_writel(*buf++, fifo, 0); + + return length; +} + +static int s3c_otg_write_fifo_ep0(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 max; + unsigned count; + int is_last; + + max = ep_maxpacket(ep); + count = s3c_otg_write_packet(ep, req, max); + + /* last packet is usually short (or a zlp) */ + if (count != max) { + is_last = 1; + } else { + if ((req->req.length != req->req.actual) || req->req.zero) + is_last = 0; + else + is_last = 1; + } + + DBG(2, "wrote %s %d bytes%s %d left %p\n", + ep->ep.name, count, is_last ? "/L" : "", + req->req.length - req->req.actual, req); + + /* requests complete when all IN data is in the FIFO */ + return is_last; +} + +static int s3c_otg_read_fifo_ep0(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 csr; + u32 *buf; + unsigned bufferspace; + unsigned count; + unsigned is_short; + unsigned bytes; + u32 fifo = ep->fifo; + + csr = s3c_otg_readl((u32)S3C_UDC_OTG_GRXSTSP); + bytes = BYTE_COUNT(csr); + + buf = req->req.buf + req->req.actual; + prefetchw(buf); + bufferspace = req->req.length - req->req.actual; + + /* read all bytes from this packet */ + if (EP_NUM(csr) == 0) { + count = bytes / 4 + (bytes % 4 ? 1 : 0); + req->req.actual += min(bytes, bufferspace); + } else { + count = 0; + bytes = 0; + } + + is_short = (bytes < ep->ep.maxpacket); + + DBG(2, "read %s %d bytes%s %d/%d\n", + ep->ep.name, bytes, is_short ? "/S" : "", + req->req.actual, req->req.length); + + while (count--) { + u32 byte = s3c_otg_readl(fifo); + + if (unlikely(bufferspace == 0)) { + /* this happens when the driver's buffer + * is smaller than what the host sent. + * discard the extra data. + */ + if (req->req.status != -EOVERFLOW) + DBG(3, "%s overflow %d\n", ep->ep.name, count); + req->req.status = -EOVERFLOW; + } else { + *buf++ = byte; + bufferspace -= 4; + } + } + + /* completion */ + if (is_short || req->req.actual == req->req.length) + return 1; + + return 0; +} + +static int s3c_otg_write_ep0(struct s3c_udc *dev) +{ + struct s3c_request *req; + struct s3c_ep *ep = &dev->ep[0]; + int ret; + int need_zlp = 0; + + if (list_empty(&ep->queue)) + req = NULL; + else + req = list_entry(ep->queue.next, struct s3c_request, queue); + + if (!req) { + DBG(2, "NULL REQ\n"); + return 0; + } + + DBG(2, "length = 0x%x, actual = 0x%x\n", + req->req.length, req->req.actual); + + if (req->req.length == 0) { + dev->ep0state = WAIT_FOR_SETUP; + s3c_otg_done(ep, req, 0); + return 1; + } + + /* Next write will end with the packet size, */ + /* so we need Zero-length-packet */ + if (req->req.length - req->req.actual == ep0_fifo_size) + need_zlp = 1; + + ret = s3c_otg_write_fifo_ep0(ep, req); + + if ((ret == 1) && !need_zlp) { + /* Last packet */ + DBG(1, "finished, waiting for status\n"); + dev->ep0state = WAIT_FOR_SETUP; + } + + if (need_zlp) { + DBG(1, "Need ZLP!\n"); + dev->ep0state = DATA_STATE_NEED_ZLP; + } + + if (ret) + s3c_otg_done(ep, req, 0); + + return ret; +} + +static int first_time = 1; + +static int s3c_otg_read_ep0(struct s3c_udc *dev) +{ + struct s3c_request *req; + struct s3c_ep *ep = &dev->ep[0]; + int ret; + + if (!list_empty(&ep->queue)) + req = list_entry(ep->queue.next, struct s3c_request, queue); + else { + DBG(3, "---> BUG\n"); + BUG(); + return 0; + } + + DBG(2, "length = 0x%x, actual = 0x%x\n", + req->req.length, req->req.actual); + + if (req->req.length == 0) { + dev->ep0state = WAIT_FOR_SETUP; + first_time = 1; + s3c_otg_done(ep, req, 0); + return 1; + } + + if (!req->req.actual && first_time) { + first_time = 0; + return 1; + } + + ret = s3c_otg_read_fifo_ep0(ep, req); + + if (ret) + s3c_otg_done(ep, req, 0); + + dev->ep0state = WAIT_FOR_SETUP; + first_time = 1; + + return ret; +} + +static void s3c_otg_kick_ep0(struct s3c_udc *dev, struct s3c_ep *ep) +{ + int res = 0; + + DBG(1, "ep_is_in = %d\n", ep_is_in(ep)); + + if (ep_is_in(ep)) { + dev->ep0state = DATA_STATE_XMIT; + while (!res) + res = s3c_otg_write_ep0(dev); + } else { + dev->ep0state = DATA_STATE_RECV; + s3c_otg_read_ep0(dev); + } +} + +/* + * Write request to FIFO + */ +static int s3c_otg_write_fifo(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 max; + u32 gintmsk; + unsigned count; + int is_last = 0; + int is_short = 0; + + gintmsk = s3c_otg_readl((u32)S3C_UDC_OTG_GINTMSK); + + max = le16_to_cpu(ep->desc->wMaxPacketSize); + count = s3c_otg_write_packet(ep, req, max); + + /* last packet is usually short (or a zlp) */ + if (count != max) { + is_last = 1; + is_short = 1; + } else { + if ((req->req.length != req->req.actual) || req->req.zero) + is_last = 0; + else + is_last = 1; + + /* interrupt/iso maxpacket may not fill the fifo */ + is_short = (max < ep_maxpacket(ep)); + } + + DBG(2, "wrote %s %d bytes%s%s %d/%d\n", + ep->ep.name, count, + is_last ? "/L" : "", is_short ? "/S" : "", + req->req.actual, req->req.length); + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + if (ep_index(ep) == 0) { + DBG(3, "--> EP0 must not come here!\n"); + BUG(); + } + + s3c_otg_writel(gintmsk & (~INT_TX_FIFO_EMPTY), + (u32)S3C_UDC_OTG_GINTMSK, 0); + s3c_otg_done(ep, req, 0); + + return 1; + } + + s3c_otg_writel(gintmsk|INT_TX_FIFO_EMPTY, + (u32)S3C_UDC_OTG_GINTMSK, 0); + + return 0; +} + +/* + * Read to request from FIFO (max read == bytes in fifo) + */ +static int s3c_otg_read_fifo(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 csr; + u32 gintmsk; + u32 *buf; + unsigned bufferspace; + unsigned count; + unsigned is_short = 0; + unsigned bytes; + u32 fifo = ep->fifo; + + csr = s3c_otg_readl((u32)S3C_UDC_OTG_GRXSTSP); + bytes = BYTE_COUNT(csr); + gintmsk = readl(S3C_UDC_OTG_GINTMSK); + + if (!bytes) { + DBG(2, "%d bytes\n", bytes); + s3c_otg_writel(INT_RX_FIFO_NOT_EMPTY, + (u32)S3C_UDC_OTG_GINTMSK, 1); + return 0; + } + + buf = req->req.buf + req->req.actual; + prefetchw(buf); + bufferspace = req->req.length - req->req.actual; + + count = bytes / 4 + (bytes % 4 ? 1 : 0); + req->req.actual += min(bytes, bufferspace); + + is_short = (bytes < ep->ep.maxpacket); + + DBG(2, "read %s %d bytes%s %d/%d\n", + ep->ep.name, bytes, is_short ? "/S" : "", + req->req.actual, req->req.length); + + while (count--) { + u32 byte = s3c_otg_readl(fifo); + + if (unlikely(bufferspace == 0)) { + /* this happens when the driver's buffer + * is smaller than what the host sent. + * discard the extra data. + */ + if (req->req.status != -EOVERFLOW) + DBG(3, "%s overflow %d\n", ep->ep.name, count); + req->req.status = -EOVERFLOW; + } else { + *buf++ = byte; + bufferspace -= 4; + } + } + + s3c_otg_writel(gintmsk|INT_RX_FIFO_NOT_EMPTY, + (u32)S3C_UDC_OTG_GINTMSK, 0); + + /* completion */ + if (is_short || req->req.actual == req->req.length) { + s3c_otg_done(ep, req, 0); + return 1; + } + + /* finished that packet. the next one may be waiting... */ + return 0; +} + +static struct usb_request *s3c_otg_alloc_request( + struct usb_ep *ep, gfp_t gfp_flags) +{ + struct s3c_request *req; + + if (!ep) + return NULL; + + DBG(1, "%s %p\n", ep->name, ep); + + req = kzalloc(sizeof *req, gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + + return &req->req; +} + +static void s3c_otg_free_request(struct usb_ep *ep, struct usb_request *_req) +{ + struct s3c_request *req; + + if (!ep) + return; + + DBG(1, "%s %p\n", ep->name, ep); + + if (!_req) + return; + + req = container_of(_req, struct s3c_request, req); + + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +/* + * Queue one request + * Kickstart transfer if needed + */ +static int s3c_otg_queue(struct usb_ep *_ep, + struct usb_request *_req, gfp_t gfp_flags) +{ + struct s3c_request *req; + struct s3c_ep *ep; + struct s3c_udc *dev; + unsigned long flags; + u32 csr; + + req = container_of(_req, struct s3c_request, req); + if (!_req || !_req->complete || !_req->buf + || !list_empty(&req->queue)) { + DBG(3, "bad params\n"); + return -EINVAL; + } + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || (!ep->desc && ep->ep.name != ep0name)) { + DBG(3, "bad ep\n"); + return -EINVAL; + } + + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + DBG(3, "bogus device state %p\n", dev->driver); + return -ESHUTDOWN; + } + + DBG(2, "%s queue req %p, len %d buf %p\n", + _ep->name, _req, _req->length, _req->buf); + + spin_lock_irqsave(&dev->lock, flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + DBG(2, "ep=%d, Q empty=%d, stopped=%d\n", + ep_index(ep), list_empty(&ep->queue), ep->stopped); + + /* kickstart this i/o queue? */ + if (list_empty(&ep->queue) && !ep->stopped) { + if (ep_index(ep) == 0) { + list_add_tail(&req->queue, &ep->queue); + s3c_otg_kick_ep0(dev, ep); + req = NULL; + } else if (ep_is_in(ep)) { + csr = s3c_otg_readl((u32)S3C_UDC_OTG_GINTSTS); + + if ((csr & INT_TX_FIFO_EMPTY) && + (s3c_otg_write_fifo(ep, req) == 1)) + req = NULL; + else + tx_ep_num = ep_index(ep); + } else { + csr = s3c_otg_readl((u32)S3C_UDC_OTG_GINTSTS); + + if ((csr & INT_RX_FIFO_NOT_EMPTY) && + (s3c_otg_read_fifo(ep, req) == 1)) + req = NULL; + } + } + + /* pio or dma irq handler advances the queue. */ + if (req) + list_add_tail(&req->queue, &ep->queue); + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/* + * dequeue JUST ONE request + */ +static int s3c_otg_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct s3c_ep *ep; + struct s3c_request *req; + unsigned long flags; + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || ep->ep.name == ep0name) + return -EINVAL; + + spin_lock_irqsave(&ep->dev->lock, flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &ep->queue, queue) { + if (&req->req == _req) + break; + } + + if (&req->req != _req) { + spin_unlock_irqrestore(&ep->dev->lock, flags); + return -EINVAL; + } + + s3c_otg_done(ep, req, -ECONNRESET); + + spin_unlock_irqrestore(&ep->dev->lock, flags); + + return 0; +} + +static int s3c_otg_set_halt(struct usb_ep *_ep, int value) +{ + return 0; +} + +static int s3c_otg_fifo_status(struct usb_ep *_ep) +{ + int count = 0; + struct s3c_ep *ep; + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep) { + DBG(3, "bad ep\n"); + return -ENODEV; + } + + /* LPD can't report unclaimed bytes from IN fifos */ + if (ep_is_in(ep)) + return -EOPNOTSUPP; + + return count; +} + +static void s3c_otg_fifo_flush(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + + ep = container_of(_ep, struct s3c_ep, ep); + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + DBG(3, "bad ep\n"); + return; + } +} + +static int s3c_otg_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct s3c_ep *ep; + struct s3c_udc *dev; + unsigned long flags; + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || !desc || ep->desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || ep->bEndpointAddress != desc->bEndpointAddress + || ep_maxpacket(ep) < le16_to_cpu(desc->wMaxPacketSize)) { + DBG(3, "bad ep or descriptor\n"); + return -EINVAL; + } + + /* xfer types must match, except that interrupt ~= bulk */ + if (ep->bmAttributes != desc->bmAttributes + && ep->bmAttributes != USB_ENDPOINT_XFER_BULK + && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { + DBG(3, "%s type mismatch\n", _ep->name); + return -EINVAL; + } + + /* hardware _could_ do smaller, but driver doesn't */ + if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK + && le16_to_cpu(desc->wMaxPacketSize) + != ep_maxpacket(ep)) + || !desc->wMaxPacketSize) { + DBG(3, "bad %s maxpacket\n", _ep->name); + return -ERANGE; + } + + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + DBG(3, "bogus device state\n"); + return -ESHUTDOWN; + } + + spin_lock_irqsave(&ep->dev->lock, flags); + + ep->stopped = 0; + ep->desc = desc; + ep->pio_irqs = 0; + ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize); + + /* Reset halt state */ + s3c_otg_set_halt(_ep, 0); + + spin_unlock_irqrestore(&ep->dev->lock, flags); + + DBG(2, "enabled %s, stopped = %d, maxpacket = %d\n", + _ep->name, ep->stopped, ep->ep.maxpacket); + return 0; +} + +static int s3c_otg_ep_disable(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + unsigned long flags; + + ep = container_of(_ep, struct s3c_ep, ep); + if (!_ep || !ep->desc) { + DBG(3, "%s not enabled\n", _ep ? ep->ep.name : NULL); + return -EINVAL; + } + + spin_lock_irqsave(&ep->dev->lock, flags); + + /* Nuke all pending requests */ + s3c_otg_nuke(ep, -ESHUTDOWN); + + ep->desc = 0; + ep->stopped = 1; + + spin_unlock_irqrestore(&ep->dev->lock, flags); + + DBG(2, "disabled %s\n", _ep->name); + return 0; +} + +static struct usb_ep_ops s3c_ep_ops = { + .enable = s3c_otg_ep_enable, + .disable = s3c_otg_ep_disable, + + .alloc_request = s3c_otg_alloc_request, + .free_request = s3c_otg_free_request, + + .queue = s3c_otg_queue, + .dequeue = s3c_otg_dequeue, + + .set_halt = s3c_otg_set_halt, + .fifo_status = s3c_otg_fifo_status, + .fifo_flush = s3c_otg_fifo_flush, +}; + +void s3c_otg_set_ep(struct s3c_udc *dev, enum usb_device_speed speed) +{ + u32 ep0_mps = DEPCTL0_MPS_64; + + if (speed == USB_SPEED_FULL) { + ep0_fifo_size = 8; + ep_fifo_size = 64; + ep_fifo_size2 = 64; + + ep0_mps = DEPCTL0_MPS_8; + } + + dev->gadget.speed = speed; + + dev->ep[0].ep.maxpacket = ep0_fifo_size; + dev->ep[1].ep.maxpacket = ep_fifo_size; + dev->ep[2].ep.maxpacket = ep_fifo_size; + dev->ep[3].ep.maxpacket = ep_fifo_size; + dev->ep[4].ep.maxpacket = ep_fifo_size; + dev->ep[5].ep.maxpacket = ep_fifo_size2; + dev->ep[6].ep.maxpacket = ep_fifo_size2; + dev->ep[7].ep.maxpacket = ep_fifo_size2; + dev->ep[8].ep.maxpacket = ep_fifo_size2; + + /* EP0 - Control */ + s3c_otg_ep_control(0, USB_DIR_OUT, ep0_mps, 1); + s3c_otg_ep_control(0, USB_DIR_IN, ep0_mps, 1); + + /* EP1 - Bulk Data OUT */ + s3c_otg_ep_control(1, USB_DIR_OUT, ep_fifo_size, 1); + + /* EP2 - Bulk Data IN */ + s3c_otg_ep_control(2, USB_DIR_IN, ep_fifo_size, 1); + + /* EP3 - INTR Data IN */ + s3c_otg_ep_control(3, USB_DIR_IN, ep_fifo_size, 1); + + DBG(2, "%s Speed Detection\n", + speed == USB_SPEED_HIGH ? "High" : "Full"); +} + +/* + * set the USB address for this device + * + * Called from control endpoint function + * after it decodes a set address setup packet. + */ +static void s3c_otg_set_address(struct s3c_udc *dev, unsigned char addr) +{ + s3c_otg_writel(DEVICE_ADDR(addr), (u32)S3C_UDC_OTG_DCFG, 1); + s3c_otg_ep_control(0, USB_DIR_IN, DEPCTL_EPENA|DEPCTL_CNAK, 1); + + DBG(2, "USB OTG 2.0 Device Address=%d\n", addr); + + dev->usb_address = addr; +} + +static inline int s3c_otg_read_setup(struct s3c_ep *ep, u32 *ctrl, int max) +{ + int bytes; + int count; + u32 csr = s3c_otg_readl((u32)S3C_UDC_OTG_GRXSTSP); + + bytes = BYTE_COUNT(csr); + + /* 32 bits interface */ + count = bytes / 4; + + while (count--) + *ctrl++ = s3c_otg_readl((u32)S3C_UDC_OTG_EP0_FIFO); + + return bytes; +} + +static void s3c_otg_setup(struct s3c_udc *dev) +{ + struct s3c_ep *ep = &dev->ep[0]; + int bytes; + int is_in; + int ret; + + /* Nuke all previous transfers */ + s3c_otg_nuke(ep, -EPROTO); + + /* read control req from fifo (8 bytes) */ + bytes = s3c_otg_read_setup(ep, (u32 *)&ctrl, 8); + + DBG(2, "SETUP REQ %02x %02x %04x %04x %d\n", + ctrl.bRequestType, ctrl.bRequest, + ctrl.wValue, ctrl.wIndex, ctrl.wLength); + + /* Set direction of EP0 */ + if (ctrl.bRequestType & USB_DIR_IN) { + ep->bEndpointAddress |= USB_DIR_IN; + is_in = 1; + } else { + ep->bEndpointAddress &= ~USB_DIR_IN; + is_in = 0; + } + + dev->req_pending = 1; + + /* Handle some SETUP packets ourselves */ + switch (ctrl.bRequest) { + case USB_REQ_SET_ADDRESS: + if (ctrl.bRequestType != (USB_TYPE_STANDARD|USB_RECIP_DEVICE)) + break; + + s3c_otg_set_address(dev, ctrl.wValue); + return; + + case USB_REQ_SET_INTERFACE: + DBG(2, "USB_REQ_SET_INTERFACE (%d)\n", ctrl.wValue); + /* FALLTHROUGH */ + + case USB_REQ_SET_CONFIGURATION: + DBG(2, "USB_REQ_SET_CONFIGURATION (%d)\n", ctrl.wValue); + + s3c_otg_ep_control(0, USB_DIR_IN, + DEPCTL_EPENA|DEPCTL_CNAK, 1); + s3c_otg_ep_control(1, USB_DIR_OUT, + DEPCTL_EPDIS|DEPCTL_CNAK| + DEPCTL_BULK_TYPE|DEPCTL_USBACTEP, 1); + s3c_otg_ep_control(2, USB_DIR_IN, + DEPCTL_BULK_TYPE|DEPCTL_USBACTEP, 1); + s3c_otg_ep_control(3, USB_DIR_IN, + DEPCTL_BULK_TYPE|DEPCTL_USBACTEP, 1); + + reset_available = 1; + dev->req_config = 1; + break; + + case USB_REQ_GET_DESCRIPTOR: + DBG(2, "USB_REQ_GET_DESCRIPTOR\n"); + break; + + case USB_REQ_GET_CONFIGURATION: + DBG(2, "USB_REQ_GET_CONFIGURATION\n"); + break; + + case USB_REQ_GET_STATUS: + DBG(2, "USB_REQ_GET_STATUS\n"); + s3c_otg_ep_control(0, USB_DIR_IN, DEPCTL_EPENA|DEPCTL_CNAK, 1); + break; + + case USB_REQ_CLEAR_FEATURE: + DBG(2, "USB_REQ_CLEAR_FEATURE\n"); + break; + + case USB_REQ_SET_FEATURE: + DBG(2, "USB_REQ_SET_FEATURE\n"); + break; + + default: + DBG(3, "Default of ctrl.bRequest=0x%x\n", ctrl.bRequest); + break; + } + + if (dev->driver) { + /* device-2-host (IN) or no data setup command, + * process immediately */ + spin_unlock(&dev->lock); + + DBG(1, "usb_ctrlrequest will be passed to fsg_setup()\n"); + + ret = dev->driver->setup(&dev->gadget, &ctrl); + spin_lock(&dev->lock); + + if (ret < 0) { + /* setup processing failed, force stall */ + DBG(3, "gadget setup FAILED (stalling) - %d\n", ret); + dev->ep0state = WAIT_FOR_SETUP; + } + } +} + +/* + * handle ep0 interrupt + */ +static void s3c_otg_handle_ep0(struct s3c_udc *dev) +{ + if (dev->ep0state == WAIT_FOR_SETUP) + s3c_otg_setup(dev); + else + DBG(3, "strange state!! - %s\n", state_names[dev->ep0state]); +} + +static void s3c_otg_handle_ep_out(struct s3c_udc *dev, u32 ep_num) +{ + struct s3c_ep *ep = &dev->ep[ep_num]; + struct s3c_request *req; + + if (unlikely(!(ep->desc))) { + /* Throw packet away.. */ + DBG(3, "No descriptor?!?\n"); + return; + } + + if (list_empty(&ep->queue)) + req = 0; + else + req = list_entry(ep->queue.next, struct s3c_request, queue); + + if (unlikely(!req)) + DBG(2, "NULL REQ on OUT EP-%d\n", ep_num); + else + s3c_otg_read_fifo(ep, req); +} + +static void s3c_otg_handle_ep_in(struct s3c_udc *dev, u32 ep_num) +{ + struct s3c_ep *ep = &dev->ep[ep_num]; + struct s3c_request *req; + + if (list_empty(&ep->queue)) + req = 0; + else + req = list_entry(ep->queue.next, struct s3c_request, queue); + + if (unlikely(!req)) { + DBG(2, "NULL REQ on IN EP-%d\n", ep_num); + return; + } else + s3c_otg_write_fifo(ep, req); +} + +static void s3c_otg_handle_ep(struct s3c_udc *dev, u32 gintmsk) +{ + u32 csr; + u32 packet_status; + u32 ep_num; + u32 bytes = 0; + + gintmsk &= ~INT_RX_FIFO_NOT_EMPTY; + s3c_otg_writel(gintmsk, (u32)S3C_UDC_OTG_GINTMSK, 0); + + csr = s3c_otg_readl((u32)S3C_UDC_OTG_GRXSTSR); + + packet_status = PKT_STS(csr); + bytes = BYTE_COUNT(csr); + ep_num = EP_NUM(csr); + + switch (packet_status) { + case SETUP_PKT_RECEIVED: + DBG(2, "SETUP received : %d bytes\n", bytes); + if (!bytes) + break; + + s3c_otg_handle_ep0(dev); + gintmsk |= INT_RX_FIFO_NOT_EMPTY; + break; + + case OUT_PKT_RECEIVED: + if (!bytes) + break; + + if (ep_num == 0) { + DBG(2, "CONTROL OUT received : %d bytes\n", bytes); + + dev->ep0state = DATA_STATE_RECV; + s3c_otg_read_ep0(dev); + + gintmsk |= INT_RX_FIFO_NOT_EMPTY; + } else if (ep_num == 1) { + DBG(2, " Bulk OUT received : %d bytes\n", bytes); + + s3c_otg_handle_ep_out(dev, 1); + gintmsk = s3c_otg_readl((u32)S3C_UDC_OTG_GINTMSK); + + s3c_otg_ep_control(1, USB_DIR_OUT, DEPCTL_CNAK, 1); + } else + DBG(3, "Unused EP%d: %d bytes\n", ep_num, bytes); + break; + + case SETUP_COMPLETED: + DBG(2, "SETUP_COMPLETED\n"); + s3c_otg_ep_control(0, USB_DIR_OUT, DEPCTL_CNAK, 1); + break; + + case OUT_COMPLELTED: + DBG(2, "OUT_COMPLELTED - ep%d\n", ep_num); + s3c_otg_ep_control(ep_num, USB_DIR_OUT, DEPCTL_CNAK, 1); + break; + + default: + gintmsk |= INT_RX_FIFO_NOT_EMPTY; + DBG(2, "reserved packet received : %d bytes\n", bytes); + break; + } + + if (!bytes) { + csr = s3c_otg_readl((u32)S3C_UDC_OTG_GRXSTSP); + gintmsk |= INT_RX_FIFO_NOT_EMPTY; + } + + s3c_otg_writel(gintmsk, (u32)S3C_UDC_OTG_GINTMSK, 0); +} + +/* + * disable USB device controller + */ +static void s3c_otg_disable(struct s3c_udc *dev) +{ + s3c_otg_set_address(dev, 0); + + dev->ep0state = WAIT_FOR_SETUP; + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->usb_address = 0; + + s3c_otg_writel(ANALOG_PWR_DOWN, (u32)S3C_USBOTG_PHYPWR, 1); +} + +/* + * initialize software state + */ +static void s3c_otg_reinit(struct s3c_udc *dev) +{ + u32 i; + + /* device/ep0 records init */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + dev->ep0state = WAIT_FOR_SETUP; + + /* basic endpoint records init */ + for (i = 0; i < S3C_MAX_ENDPOINTS; i++) { + struct s3c_ep *ep = &dev->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + + ep->desc = 0; + ep->stopped = 0; + INIT_LIST_HEAD(&ep->queue); + ep->pio_irqs = 0; + } +} + +#define GUSBCFG_INIT (PHY_CLK_480M|TXFIFO_RE_EN| \ + TURN_AROUND|HNP_DISABLE|SRP_DISABLE|ULPI_DDR| \ + HS_UTMI|INTERF_UTMI|PHY_INTERF_16|TIME_OUT_CAL) + +#define GINTMSK_INIT (INT_RESUME|INT_ENUMDONE| \ + INT_RESET|INT_SUSPEND|INT_RX_FIFO_NOT_EMPTY) + +#define DOEPMSK_INIT (AHB_ERROR) + +#define DIEPMSK_INIT (IN_EP_TIMEOUT|AHB_ERROR) + +#define GAHBCFG_INIT (PTXFE_HALF|NPTXFE_HALF| \ + MODE_SLAVE|BURST_INCR16|GBL_INT_UNMASK) + +static void s3c_otg_config(void) +{ + u32 reg; + + /* OTG USB configuration */ + s3c_otg_writel(GUSBCFG_INIT, (u32)S3C_UDC_OTG_GUSBCFG, 0); + + /* Soft-reset OTG Core and then unreset again */ + s3c_otg_writel(CORE_SOFT_RESET, (u32)S3C_UDC_OTG_GRSTCTL, 0); + + /* Put the OTG device core in the disconnected state */ + s3c_otg_writel(SOFT_DISCONNECT, (u32)S3C_UDC_OTG_DCTL, 1); + + udelay(20); + + /* Make the OTG device core exit from the disconnected state */ + reg = s3c_otg_readl((u32)S3C_UDC_OTG_DCTL); + s3c_otg_writel(reg & ~SOFT_DISCONNECT, (u32)S3C_UDC_OTG_DCTL, 0); + + /* Configure OTG Core to initial settings of device mode */ + s3c_otg_writel(EP_MIS_CNT(0x1)|SPEED_2_HIGH, (u32)S3C_UDC_OTG_DCFG, 1); + + udelay(1000); + + /* Unmask the core interrupts */ + s3c_otg_writel(GINTMSK_INIT, (u32)S3C_UDC_OTG_GINTMSK, 0); + + /* Set NAK bit of EP0, EP1, EP2 */ + s3c_otg_ep_control(0, USB_DIR_OUT, + DEPCTL_EPDIS|DEPCTL_SNAK|DEPCTL0_MPS_64, 0); + s3c_otg_ep_control(0, USB_DIR_IN, + DEPCTL_EPDIS|DEPCTL_SNAK|DEPCTL0_MPS_64, 0); + s3c_otg_ep_control(1, USB_DIR_OUT, + DEPCTL_EPDIS|DEPCTL_SNAK|DEPCTL_BULK_TYPE, 0); + s3c_otg_ep_control(2, USB_DIR_IN, + DEPCTL_EPDIS|DEPCTL_SNAK|DEPCTL_BULK_TYPE, 0); + s3c_otg_ep_control(3, USB_DIR_IN, + DEPCTL_EPDIS|DEPCTL_SNAK|DEPCTL_BULK_TYPE, 0); + + /* Unmask EP interrupts */ + s3c_otg_writel(S3C_UDC_INT_IN_EP0 + |S3C_UDC_INT_IN_EP2 + |S3C_UDC_INT_IN_EP3 + |S3C_UDC_INT_OUT_EP0 + |S3C_UDC_INT_OUT_EP1, + (u32)S3C_UDC_OTG_DAINTMSK, 0); + + /* Unmask device OUT EP common interrupts */ + s3c_otg_writel(DOEPMSK_INIT, (u32)S3C_UDC_OTG_DOEPMSK, 0); + + /* Unmask device IN EP common interrupts */ + s3c_otg_writel(DIEPMSK_INIT, (u32)S3C_UDC_OTG_DIEPMSK, 0); + + /* Set Rx FIFO Size */ + s3c_otg_writel(RX_FIFO_SIZE, (u32)S3C_UDC_OTG_GRXFSIZ, 0); + + /* Set Non Periodic Tx FIFO Size */ + s3c_otg_writel(NPTX_FIFO_SIZE|NPTX_FIFO_START_ADDR, + (u32)S3C_UDC_OTG_GNPTXFSIZ, 0); + + /* Clear NAK bit of EP0 For Slave mode */ + s3c_otg_ep_control(0, USB_DIR_OUT, DEPCTL_EPDIS|DEPCTL_CNAK, 0); + + /* Initialize OTG Link Core */ + s3c_otg_writel(GAHBCFG_INIT, (u32)S3C_UDC_OTG_GAHBCFG, 0); +} + +static int s3c_otg_enable(struct s3c_udc *dev) +{ + /* USB_SIG_MASK */ + s3c_otg_writel(S3C64XX_OTHERS_USBMASK, (u32)S3C64XX_OTHERS, 1); + + /* Initializes OTG Phy. */ + s3c_otg_writel(SUSPEND_DISABLE, (u32)S3C_USBOTG_PHYPWR, 0); + s3c_otg_writel(dev->phyclk, (u32)S3C_USBOTG_PHYCLK, 0); + + s3c_otg_writel(SW_RST_ON, (u32)S3C_USBOTG_RSTCON, 0); + udelay(50); + + s3c_otg_writel(SW_RST_OFF, (u32)S3C_USBOTG_RSTCON, 0); + udelay(50); + + s3c_otg_config(); + + DBG(2, "S3C USB 2.0 OTG Controller Core Initialized\n"); + + dev->gadget.speed = USB_SPEED_UNKNOWN; + + return 0; +} + +/* + * usb client interrupt handler. + */ +static irqreturn_t s3c_otg_irq(int irq, void *_dev) +{ + struct s3c_udc *dev = _dev; + u32 intr_status; + u32 usb_status; + u32 gintmsk; + + spin_lock(&dev->lock); + + intr_status = s3c_otg_readl((u32)S3C_UDC_OTG_GINTSTS); + gintmsk = s3c_otg_readl((u32)S3C_UDC_OTG_GINTMSK); + + DBG(1, "GINTSTS=0x%x(on state %s), GINTMSK : 0x%x\n", + intr_status, state_names[dev->ep0state], gintmsk); + + if (!intr_status) { + spin_unlock(&dev->lock); + return IRQ_HANDLED; + } + + if (intr_status & INT_ENUMDONE) { + DBG(2, "Speed Detection interrupt\n"); + s3c_otg_writel(INT_ENUMDONE, (u32)S3C_UDC_OTG_GINTSTS, 0); + + usb_status = ENUM_SPEED(s3c_otg_readl((u32)S3C_UDC_OTG_DSTS)); + + if (usb_status & (USB_FULL_30_60MHZ | USB_FULL_48MHZ)) + s3c_otg_set_ep(dev, USB_SPEED_FULL); + else + s3c_otg_set_ep(dev, USB_SPEED_HIGH); + } + + if (intr_status & INT_EARLY_SUSPEND) { + DBG(2, "Early suspend interrupt\n"); + s3c_otg_writel(INT_EARLY_SUSPEND, (u32)S3C_UDC_OTG_GINTSTS, 0); + } + + if (intr_status & INT_SUSPEND) { + DBG(2, "Suspend interrupt\n"); + s3c_otg_writel(INT_SUSPEND, (u32)S3C_UDC_OTG_GINTSTS, 0); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->suspend) { + dev->driver->suspend(&dev->gadget); + } + } + + if (intr_status & INT_RESUME) { + DBG(2, "Resume interrupt\n"); + s3c_otg_writel(INT_RESUME, (u32)S3C_UDC_OTG_GINTSTS, 0); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->resume) { + dev->driver->resume(&dev->gadget); + } + } + + if (intr_status & INT_RESET) { + DBG(2, "Reset interrupt\n"); + s3c_otg_writel(INT_RESET, (u32)S3C_UDC_OTG_GINTSTS, 0); + + usb_status = s3c_otg_readl((u32)S3C_UDC_OTG_GOTGCTL); + + if (usb_status | (A_SESSION_VALID|B_SESSION_VALID)) { + if (reset_available) { + s3c_otg_config(); + dev->ep0state = WAIT_FOR_SETUP; + reset_available = 0; + } + } else { + reset_available = 1; + DBG(2, "RESET handling skipped\n"); + } + } + + if (intr_status & INT_RX_FIFO_NOT_EMPTY) { + s3c_otg_handle_ep(dev, gintmsk); + spin_unlock(&dev->lock); + + return IRQ_HANDLED; + } + + + if (intr_status & INT_TX_FIFO_EMPTY) { + DBG(2, "INT_TX_FIFO_EMPTY ep_num=%d\n", tx_ep_num); + s3c_otg_handle_ep_in(dev, tx_ep_num); + } + + spin_unlock(&dev->lock); + + return IRQ_HANDLED; +} + +static void s3c_otg_stop_activity(struct s3c_udc *dev, + struct usb_gadget_driver *driver) +{ + int i; + + /* don't disconnect drivers more than once */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = 0; + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < S3C_MAX_ENDPOINTS; i++) { + struct s3c_ep *ep = &dev->ep[i]; + ep->stopped = 1; + s3c_otg_nuke(ep, -ESHUTDOWN); + } + + /* report disconnect; the driver is already quiesced */ + if (driver) { + spin_unlock(&dev->lock); + driver->disconnect(&dev->gadget); + spin_lock(&dev->lock); + } + + /* re-init driver-visible data structures */ + s3c_otg_reinit(dev); +} + +/* + * Register the gadget driver. Used by gadget drivers when + * registering themselves with the controller. + */ +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct s3c_udc *dev = the_controller; + int retval; + + if (!driver + || driver->speed != USB_SPEED_HIGH + || !driver->bind + || !driver->setup) + return -EINVAL; + + if (!dev) { + DBG(3, "No device\n"); + return -ENODEV; + } + + if (dev->driver) { + DBG(3, "Already bound to %s\n", driver->driver.name); + return -EBUSY; + } + + /* first hook up the driver ... */ + dev->driver = driver; + dev->gadget.dev.driver = &driver->driver; + retval = device_add(&dev->gadget.dev); + + if (retval) { /* TODO */ + DBG(3, "target device_add failed, error %d\n", retval); + return retval; + } + + retval = driver->bind(&dev->gadget); + if (retval) { + DBG(3, "%s: bind to driver %s --> error %d\n", + dev->gadget.name, driver->driver.name, retval); + device_del(&dev->gadget.dev); + + dev->driver = 0; + dev->gadget.dev.driver = 0; + return retval; + } + + printk("Registered gadget driver '%s'\n", driver->driver.name); + s3c_otg_enable(dev); + + enable_irq(IRQ_OTG); + + return 0; +} +EXPORT_SYMBOL(usb_gadget_register_driver); + +/* + Unregister entry point for the peripheral controller driver. +*/ +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct s3c_udc *dev = the_controller; + unsigned long flags; + + if (!dev) + return -ENODEV; + + if (!driver || driver != dev->driver) + return -EINVAL; + + spin_lock_irqsave(&dev->lock, flags); + + dev->driver = 0; + s3c_otg_stop_activity(dev, driver); + + spin_unlock_irqrestore(&dev->lock, flags); + + if (driver->unbind) + driver->unbind(&dev->gadget); + + device_del(&dev->gadget.dev); + + disable_irq(IRQ_OTG); + + printk(KERN_INFO "Unregistered gadget driver '%s'\n", + driver->driver.name); + + s3c_otg_disable(dev); + + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +/* + * device-scoped parts of the api to the usb controller hardware + */ +static int s3c_otg_get_frame(struct usb_gadget *gadget) +{ + u32 frame = s3c_otg_readl((u32)S3C_UDC_OTG_DSTS); + return FRAME_CNT(frame); +} + +static int s3c_otg_wakeup(struct usb_gadget *gadget) +{ + return -EOPNOTSUPP; +} + +static int s3c_otg_set_selfpowered( + struct usb_gadget *gadget, int is_selfpowered) +{ + return -EOPNOTSUPP; +} + +static int s3c_otg_pullup(struct usb_gadget *gadget, int is_on) +{ + return -EOPNOTSUPP; +} + +static int s3c_otg_vbus_session(struct usb_gadget *gadget, int is_active) +{ + return -EOPNOTSUPP; +} + +static int s3c_otg_vbus_draw(struct usb_gadget *gadget, unsigned mA) +{ + return -EOPNOTSUPP; +} + +static const struct usb_gadget_ops s3c_udc_ops = { + .get_frame = s3c_otg_get_frame, + .wakeup = s3c_otg_wakeup, + .set_selfpowered = s3c_otg_set_selfpowered, + .vbus_session = s3c_otg_vbus_session, + .vbus_draw = s3c_otg_vbus_draw, + .pullup = s3c_otg_pullup, +}; + +static void nop_release(struct device *dev) +{ + DBG(2, "%s\n", dev->bus_id); +} + +static struct s3c_udc memory = { + .usb_address = 0, + .gadget = { + .ops = &s3c_udc_ops, + .ep0 = &memory.ep[0].ep, + .name = driver_name, + .dev = { + .bus_id = "gadget", + .release = nop_release, + }, + }, + .ep[0] = { + .ep = { + .name = ep0name, + .ops = &s3c_ep_ops, + .maxpacket = EP0_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = 0, + .bmAttributes = 0, + + .ep_type = ep_control, + .fifo = (u32) S3C_UDC_OTG_EP0_FIFO, + }, + .ep[1] = { + .ep = { + .name = "ep1-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_out, + .fifo = (u32) S3C_UDC_OTG_EP1_FIFO, + }, + .ep[2] = { + .ep = { + .name = "ep2-bulk", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 2, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + + .ep_type = ep_bulk_in, + .fifo = (u32) S3C_UDC_OTG_EP2_FIFO, + }, + + .ep[3] = { + .ep = { + .name = "ep3-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 3, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (u32) S3C_UDC_OTG_EP3_FIFO, + }, + .ep[4] = { + .ep = { + .name = "ep4-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 4, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (u32) S3C_UDC_OTG_EP4_FIFO, + }, + .ep[5] = { + .ep = { + .name = "ep5-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE2, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 5, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (u32) S3C_UDC_OTG_EP5_FIFO, + }, + .ep[6] = { + .ep = { + .name = "ep6-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE2, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 6, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (u32) S3C_UDC_OTG_EP6_FIFO, + }, + .ep[7] = { + .ep = { + .name = "ep7-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE2, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 7, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (u32) S3C_UDC_OTG_EP7_FIFO, + }, + .ep[8] = { + .ep = { + .name = "ep8-int", + .ops = &s3c_ep_ops, + .maxpacket = EP_FIFO_SIZE2, + }, + .dev = &memory, + + .bEndpointAddress = USB_DIR_IN | 8, + .bmAttributes = USB_ENDPOINT_XFER_INT, + + .ep_type = ep_interrupt, + .fifo = (u32) S3C_UDC_OTG_EP8_FIFO, + }, +}; + +static struct clk *otg_clock; + +/* + * binds to the platform device + */ +static int s3c_otg_probe(struct platform_device *pdev) +{ + struct s3c_udc *dev = &memory; + struct s3c_plat_otg_data *pdata = pdev->dev.platform_data; + int retval; + + DBG(2, "%p\n", pdev); + + spin_lock_init(&dev->lock); + dev->dev = pdev; + + device_initialize(&dev->gadget.dev); + dev->gadget.dev.parent = &pdev->dev; + + dev->gadget.is_dualspeed = 1; + dev->gadget.is_otg = 0; + dev->gadget.is_a_peripheral = 0; + dev->gadget.b_hnp_enable = 0; + dev->gadget.a_hnp_support = 0; + dev->gadget.a_alt_hnp_support = 0; + + dev->phyclk = pdata->phyclk; + + the_controller = dev; + platform_set_drvdata(pdev, dev); + + otg_clock = clk_get(&pdev->dev, "otg"); + if (otg_clock == NULL) { + DBG(3, "failed to find otg clock source\n"); + return -ENOENT; + } + clk_enable(otg_clock); + + s3c_otg_reinit(dev); + + local_irq_disable(); + + /* irq setup after old hardware state is cleaned up */ + retval = request_irq(IRQ_OTG, s3c_otg_irq, + IRQF_DISABLED, driver_name, dev); + + if (retval != 0) { + DBG(3, "%s: can't get irq %i - %d\n", + driver_name, IRQ_OTG, retval); + return -EBUSY; + } + + disable_irq(IRQ_OTG); + local_irq_enable(); + create_proc_files(); + + return retval; +} + +static int s3c_otg_remove(struct platform_device *pdev) +{ + struct s3c_udc *dev = platform_get_drvdata(pdev); + + if (otg_clock != NULL) { + clk_disable(otg_clock); + clk_put(otg_clock); + otg_clock = NULL; + } + + remove_proc_files(); + usb_gadget_unregister_driver(dev->driver); + + free_irq(IRQ_OTG, dev); + + platform_set_drvdata(pdev, 0); + + the_controller = 0; + + return 0; +} + +#ifdef CONFIG_PM +static int s3c_otg_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct s3c_udc *dev = the_controller; + + if (dev->driver) { + disable_irq(IRQ_OTG); + s3c_otg_disable(dev); + clk_disable(otg_clock); + } + + return 0; +} + +static int s3c_otg_resume(struct platform_device *pdev) +{ + struct s3c_udc *dev = the_controller; + + if (dev->driver) { + clk_enable(otg_clock); + s3c_otg_enable(dev); + s3c_otg_reinit(dev); + enable_irq(IRQ_OTG); + } + + return 0; +} +#else +#define s3c_otg_suspend NULL +#define s3c_otg_resume NULL +#endif + +/*-------------------------------------------------------------------------*/ +static struct platform_driver s3c_otg_driver = { + .probe = s3c_otg_probe, + .remove = s3c_otg_remove, + .suspend = s3c_otg_suspend, + .resume = s3c_otg_resume, + .driver = { + .owner = THIS_MODULE, + .name = "s3c-otg-usbgadget", + }, +}; + +static int __init otg_init(void) +{ + int ret; + + ret = platform_driver_register(&s3c_otg_driver); + if (!ret) + printk(KERN_INFO "Loaded %s version %s %s\n", + driver_name, DRIVER_VERSION, "(Slave Mode)"); + + return ret; +} + +static void __exit otg_exit(void) +{ + platform_driver_unregister(&s3c_otg_driver); + printk(KERN_INFO "Unloaded %s version %s\n", + driver_name, DRIVER_VERSION); +} + +module_init(otg_init); +module_exit(otg_exit); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); -- cgit v1.2.3