This patch add experimental driver for S3C OTG chip in UDC mode. The driver is based on Samsung BSP driver code, but it is heavily updated and bugfixed. It works only in Slave (CPU driven) mode. I did not work on DMA mode yet. It looks that the chip has some undocumented hardware bugs (or features), so I had to add several workaround in the driver code (see comments). The driver has been developed and tested on NCP board (ARM S3C6410 based). Reviewed-by: Kyungmin Park <kyungmin.park@xxxxxxxxxxx> Signed-off-by: Marek Szyprowski <m.szyprowski@xxxxxxxxxxx> diff --git a/arch/arm/plat-s3c/dev-usb-hsotg.c b/arch/arm/plat-s3c/dev-usb-hsotg.c index e2f604b..d8397f8 100644 --- a/arch/arm/plat-s3c/dev-usb-hsotg.c +++ b/arch/arm/plat-s3c/dev-usb-hsotg.c @@ -19,6 +19,7 @@ #include <mach/map.h> #include <plat/devs.h> +#include <plat/udc-hs.h> static struct resource s3c_usb_hsotg_resources[] = { [0] = { @@ -39,3 +40,19 @@ struct platform_device s3c_device_usb_hsotg = { .num_resources = ARRAY_SIZE(s3c_usb_hsotg_resources), .resource = s3c_usb_hsotg_resources, }; + +void __init s3c_hsotg_set_platdata(struct s3c_hsotg_plat *pd) +{ + struct s3c_hsotg_plat *npd; + + if (!pd) { + printk(KERN_ERR "%s: no platform data\n", __func__); + return; + } + + npd = kmemdup(pd, sizeof(struct s3c_hsotg_plat), GFP_KERNEL); + if (!npd) + printk(KERN_ERR "%s: no memory for platform data\n", __func__); + + s3c_device_usb_hsotg.dev.platform_data = npd; +} 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 0000000..389655f --- /dev/null +++ b/arch/arm/plat-s3c/include/plat/regs-usb-hs-otg.h @@ -0,0 +1,350 @@ +/* linux/include/asm-arm/arch-s3c2410/regs-udc.h + * + * Copyright (C) 2008 Samsung Electronics + * Copyright (C) 2004 Herbert Poetzl <herbert@xxxxxxxxxxxx> + * + * 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 */ +/* Core Global Registers */ +#define S3C_USBOTGREG(x) (x) +/* 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 x Control */ +#define S3C_UDC_OTG_DIEPCTL(x) (S3C_USBOTGREG(0x900) + ((x)<<5)) +/* Device IN Endpoint x Interrupt */ +#define S3C_UDC_OTG_DIEPINT(x) (S3C_USBOTGREG(0x908) + ((x)<<5)) +/* Device IN Endpoint x Transfer Size */ +#define S3C_UDC_OTG_DIEPTSIZ(x) (S3C_USBOTGREG(0x910) + ((x)<<5)) +/* Device IN Endpoint x DMA Address */ +#define S3C_UDC_OTG_DIEPDMA(x) (S3C_USBOTGREG(0x914) + ((x)<<5)) + +/* 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 x Control */ +#define S3C_UDC_OTG_DOEPCTL(x) (S3C_USBOTGREG(0xB00) + ((x)<<5)) +/* Device OUT Endpoint x Interrupt */ +#define S3C_UDC_OTG_DOEPINT(x) (S3C_USBOTGREG(0xB08) + ((x)<<5)) +/* Device OUT Endpoint x Transfer Size */ +#define S3C_UDC_OTG_DOEPTSIZ(x) (S3C_USBOTGREG(0xB10) + ((x)<<5)) +/* Device OUT Endpoint x DMA Address */ +#define S3C_UDC_OTG_DOEPDMA(x) (S3C_USBOTGREG(0xB14) + ((x)<<5)) + +/* 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_EP_FIFO(x) S3C_USBOTGREG(0x1000 + ((x) * 0x1000)) + +/* 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) +#define CORE_TX_FIFO_FLUSH (0x1<<5) + +/* 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_PTX_FIFO_EMPTY (0x1<<26) +#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)) +#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_EP(x) (0x1<<(x)) +#define S3C_UDC_INT_OUT_EP(x) (0x1<<(16 + (x))) + +/* 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) + +/* S3C_UDC_OTG_GNPTXSTS */ +#define GNPTXSTS_NPTXQSPCAVAIL (0xFF<<16) + +#endif diff --git a/arch/arm/plat-s3c/include/plat/udc-hs.h b/arch/arm/plat-s3c/include/plat/udc-hs.h index dd04db0..da8c217 100644 --- a/arch/arm/plat-s3c/include/plat/udc-hs.h +++ b/arch/arm/plat-s3c/include/plat/udc-hs.h @@ -26,4 +26,13 @@ enum s3c_hostg_dmamode { struct s3c_hsotg_plat { enum s3c_hostg_dmamode dma; unsigned int is_osc : 1; + int gpio_usb_detect; }; + +/** + * s3c_usbgadget_set_platdata() - Setup the USB HS OTG device with platform data. + * @pd: The platform data to set. The data is copied from the passed structure + * so the machine data can mark the data __initdata so that any unused + * machines will end up dumping their data at runtime. + */ +extern void s3c_hsotg_set_platdata(struct s3c_hsotg_plat *pd); diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 5d1ddf4..745d3d3 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -307,6 +307,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_hs_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_S3C + tristate + depends on 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 e6017e6..80e3e24 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o obj-$(CONFIG_USB_CI13XXX) += ci13xxx_udc.o obj-$(CONFIG_USB_S3C_HSOTG) += s3c-hsotg.o +obj-$(CONFIG_USB_S3C) += s3c_hs_udc.o obj-$(CONFIG_USB_LANGWELL) += langwell_udc.o # diff --git a/drivers/usb/gadget/s3c_hs_udc.c b/drivers/usb/gadget/s3c_hs_udc.c new file mode 100644 index 0000000..c006983 --- /dev/null +++ b/drivers/usb/gadget/s3c_hs_udc.c @@ -0,0 +1,2178 @@ +/* + * drivers/usb/gadget/s3c_hs_udc.c + * Samsung S3C on-chip full/high speed USB OTG 2.0 device controllers + * + * Copyright (C) 2008-2009 Samsung Electronics + * Minkyu Kang <mk7.kang@xxxxxxxxxxx> + * Michal Lewandowski <m.lewandowski@xxxxxxxxxxx> + * Marek Szyprowski <m.szyprowski@xxxxxxxxxxx> + * + * 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 + * + */ +/* + * S3C OTG UDC supports 2 modes of operation: DMA and Slave (CPU driven) + * This driver supports only the Slave mode. + * + * The hardware has 10 FIFOs: + * 1 RX FIFO, + * 1 NonPeriodic TX FIFO + * 8 Periodic FIFOs. + * + * RX fifo is used for OUT packets. + * NonPeriodic TX FIFO is used for Control Requestes and BULT IN transfers. + * Periodic TX FIFO should be used for Interrupt and Iso transfers, but... + * ... I did not manage to get them working correctly. When used after a few + * packet some random data has been transfered from that FIFOs. + * + * To workaround this problem Interrupt packets are also sent from the main + * NonPeriodic TX FIFO, but only after a proper IN token has been received + * (otherwise they would block the main NonPeriodic TX FIFO until a token + * is received). + * + */ + +#include "s3c_hs_udc.h" +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <mach/map.h> +#include <plat/regs-clock.h> +#include <plat/regs-usb-hs-otg.h> +#include <plat/regs-usb-hsotg-phy.h> +#include <plat/regs-sys.h> +#include <plat/udc-hs.h> +#include <linux/gpio.h> + +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 "01 Jul 2009" + +struct s3c_udc *the_controller; + +static const char driver_name[] = "s3c_hs_udc"; +static const char driver_desc[] = DRIVER_DESC; +static const char ep0name[] = "ep0-control"; + +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; +static int tx_stopped; + +static int current_ep = NO_EP_ACTIVE; + +#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 */ + + +/*** register io function ***/ + +/* + * read otg register + */ +static inline u32 s3c_otg_readl(u32 reg) +{ + struct s3c_udc *dev = the_controller; + return readl(dev->regs + reg); +} + +/* + * write otg register + */ +static inline void s3c_otg_writel(u32 val, u32 reg) +{ + struct s3c_udc *dev = the_controller; + writel(val, dev->regs + reg); +} + +/* + * set ep control req + */ +static void s3c_otg_ep_control(int ep, int dir, u32 val, int update) +{ + u32 epctrl; + + if (dir) + epctrl = (S3C_UDC_OTG_DIEPCTL(ep)); + else + epctrl = (S3C_UDC_OTG_DOEPCTL(ep)); + + if (update) + s3c_otg_writel(val | s3c_otg_readl(epctrl), epctrl); + else + s3c_otg_writel(val, epctrl); +} + +/* + * set end point interrupt mask + */ +static inline void s3c_otg_set_ep_int_mask(int ep_num, int dir, int value) +{ + u32 mask; + if (dir) + mask = S3C_UDC_INT_IN_EP(ep_num); + else + mask = S3C_UDC_INT_OUT_EP(ep_num); + + if (value) { + u32 tmp = s3c_otg_readl(S3C_UDC_OTG_DAINTMSK); + tmp |= mask; + DBG(1, "ep %d interrupt enabled\n", ep_num); + s3c_otg_writel(tmp, S3C_UDC_OTG_DAINTMSK); + } else { + u32 tmp = s3c_otg_readl(S3C_UDC_OTG_DAINTMSK); + tmp &= ~mask; + s3c_otg_writel(tmp, S3C_UDC_OTG_DAINTMSK); + DBG(1, "ep %d interrupt disabled\n", ep_num); + } +} + +/* + * set global otg interrupt mask + */ +static inline void s3c_otg_set_int_mask(u32 bit, int value) +{ + if (value) { + u32 tmp = s3c_otg_readl(S3C_UDC_OTG_GINTMSK); + tmp |= bit; + s3c_otg_writel(tmp, S3C_UDC_OTG_GINTMSK); + DBG(1, "interrupt bit %08x enabled\n", bit); + } else { + u32 tmp = s3c_otg_readl(S3C_UDC_OTG_GINTMSK); + tmp &= ~(bit); + s3c_otg_writel(tmp, S3C_UDC_OTG_GINTMSK); + DBG(1, "interrupt bit %08x disabled\n", bit); + } +} + +/* + * write request to fifo register + */ +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; + + 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); + + epsize = (S3C_UDC_OTG_DIEPTSIZ(ep_index(ep))); + + s3c_otg_writel(PKT_CNT(0x1)|XFERSIZE(length), epsize); + s3c_otg_ep_control(ep_index(ep), USB_DIR_IN, + DEPCTL_EPENA|DEPCTL_CNAK, 1); + + /* lenght in bytes */ + for (count = 0; count < length; count += 4) + s3c_otg_writel(*buf++, fifo); + + return length; +} + +/*** request control functions ***/ + +/* + * 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; + + req->req.complete(&ep->ep, &req->req); + + 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); + } +} + +/* + * Write data to ep0 fifo + */ +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; +} + +/* + * Read data from ep0 fifo + */ +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(S3C_UDC_OTG_GRXSTSP); + bytes = BYTE_COUNT(csr); + + buf = req->req.buf + req->req.actual; + 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; + + 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; + } + + ret = s3c_otg_write_fifo_ep0(ep, req); + + /* there is no need to send (optional) zlp manually + hardware will do it for us */ + + if (ret == 1) { + /* Last packet */ + DBG(1, "finished, waiting for status\n"); + dev->ep0state = WAIT_FOR_SETUP; + } + + 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; +} + +/* + * Kickstart a request for ep0 + */ +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; + unsigned count; + int is_last = 0; + int is_short = 0; + + current_ep = ep_index(ep); + + 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); + + s3c_otg_set_int_mask(INT_TX_FIFO_EMPTY, 1); + DBG(2, "INT_TX_FIFO_EMPTY enabled\n"); + + if (is_last) { + DBG(2, "packet completed succesfully\n"); + current_ep = NO_EP_ACTIVE; + s3c_otg_done(ep, req, 0); + if (ep->ep_type != ep_interrupt) + ep->got_token = 1; + else + ep->got_token = 0; + } else { + DBG(2, "packet not completed yet!\n"); + /* Do not start writing to txfifo here, hardware will loose + packet completition irq in such case. Let it be started + from INT_TX_FIFO_EMPTY irq */ + } + + /* enable INT_TX_FIFO_EMPTY irq to send next packet from IN queue(s) */ + s3c_otg_set_int_mask(INT_TX_FIFO_EMPTY, 1); + + /* enable ep interrupt for collecting next token */ + if (ep->ep_type == ep_interrupt && !list_empty(&ep->queue)) { + s3c_otg_set_ep_int_mask(ep_index(ep), USB_DIR_IN, 1); + DBG(2, "token int enabled for ep %d\n", ep_index(ep)); + } + + return 1; +} + +/* + * Mark that token for specified EP arrived + */ +static int s3c_otg_handle_in_token(struct s3c_udc *dev, int num) +{ + struct s3c_ep *ep = &dev->ep[num]; + + ep->got_token = 1; + + s3c_otg_set_int_mask(INT_TX_FIFO_EMPTY, 1); + DBG(2, "INT_TX_FIFO_EMPTY enabled\n"); + DBG(2, "got token for ep %d\n", num); + s3c_otg_set_ep_int_mask(ep_index(ep), USB_DIR_IN, 0); + + return 0; +} + +/* + * Read data from endpoint FIFO (max read == bytes in fifo) + */ +static int s3c_otg_read_fifo(struct s3c_ep *ep, struct s3c_request *req) +{ + u32 csr; + u32 *buf; + unsigned bufferspace; + unsigned count; + unsigned is_short = 0; + unsigned bytes; + u32 fifo = ep->fifo; + + csr = s3c_otg_readl(S3C_UDC_OTG_GRXSTSP); + bytes = BYTE_COUNT(csr); + + if (!bytes) { + DBG(2, "%d bytes\n", bytes); + s3c_otg_set_int_mask(INT_RX_FIFO_NOT_EMPTY, 1); + DBG(2, "INT_RX_FIFO_NOT_EMPTY enabled\n"); + return 0; + } + + buf = req->req.buf + req->req.actual; + 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_set_int_mask(INT_RX_FIFO_NOT_EMPTY, 1); + DBG(2, "INT_RX_FIFO_NOT_EMPTY enabled\n"); + + /* 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; +} + +/* + * Kickstart a request for IN ep + */ +static void s3c_otg_kick_ep_in(struct s3c_ep *ep, struct s3c_request *req) +{ + /* is the endpoint ready for transmitting data? */ + if (ep->got_token) { + /* is the NPTX FIFO ready for transmitting data? */ + if (!tx_stopped && current_ep == NO_EP_ACTIVE) { + u32 csr = s3c_otg_readl(S3C_UDC_OTG_GINTSTS); + if ((csr & INT_TX_FIFO_EMPTY)) + s3c_otg_write_fifo(ep, req); + else + DBG(2, "tx fifo not empty!\n"); + } else { + DBG(2, "ep cannot transfer data now\n"); + } + } else { + /* enable end point interrupt, clear NAK bit and wait + for token */ + s3c_otg_set_ep_int_mask(ep_index(ep), USB_DIR_IN, 1); + s3c_otg_ep_control(ep_index(ep), USB_DIR_IN, DEPCTL_CNAK, 1); + DBG(2, "token irq enabled\n"); + } +} + +/* + * Kickstart a request for OUT ep + */ +static void s3c_otg_kick_ep_out(struct s3c_ep *ep) +{ + /* clear NAK bit and wait for RX FIFO not empty interrupt, + packet data will be read in the irs */ + s3c_otg_ep_control(ep_index(ep), USB_DIR_OUT, DEPCTL_CNAK, 1); + s3c_otg_set_int_mask(INT_RX_FIFO_NOT_EMPTY, 1); + DBG(2, "INT_RX_FIFO_NOT_EMPTY enabled\n"); +} + + +/*** Gadget Callbacks ***/ + +/* + * Alloc a request structure + */ +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; +} + +/* + * Free a request structure + */ +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; + int queue_empty; + + req = container_of(_req, struct s3c_request, req); + if (!_req || !_req->complete || !_req->buf + || !list_empty(&req->queue)) { + DBG(3, "bad params\n"); + if (!_req) + DBG(3, "req == NULL\n"); + if (!_req->complete) + DBG(3, "req->complete == NULL\n"); + if (!_req->buf) + DBG(3, "req->buf == NULL\n"); + if (!list_empty(&req->queue)) + DBG(3, "list_empty(&req->queue)\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); + + local_irq_save(flags); + + _req->status = -EINPROGRESS; + _req->actual = 0; + + DBG(2, "ep=%d, Q empty=%d, stopped=%d, tx_stopped=%d\n", + ep_index(ep), list_empty(&ep->queue), ep->stopped, tx_stopped); + + queue_empty = list_empty(&ep->queue); + + list_add_tail(&req->queue, &ep->queue); + + /* kickstart this i/o queue? */ + if (queue_empty && !ep->stopped) { + if (ep_index(ep) == 0) { + /* control request */ + s3c_otg_kick_ep0(dev, ep); + } else if (ep_is_in(ep)) { + /* IN request */ + s3c_otg_kick_ep_in(ep, req); + } else { + /* OUT request */ + s3c_otg_kick_ep_out(ep); + } + } + + local_irq_restore(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; + + local_irq_save(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) { + local_irq_restore(flags); + return -EINVAL; + } + + s3c_otg_done(ep, req, -ECONNRESET); + + local_irq_restore(flags); + + return 0; +} + +/* noop... */ +static int s3c_otg_set_halt(struct usb_ep *_ep, int value) +{ + return 0; +} + +/* noop... */ +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; +} + +/* noop... */ +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; + } +} + + +/* + * Enable end point, configure it basing on the supplied descriptor + */ +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) { + DBG(3, "bad ep or descriptor\n"); + return -EINVAL; + } + + /* hardware _could_ do smaller, but driver doesn't */ + if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK + && le16_to_cpu(desc->wMaxPacketSize) + != EP_FIFO_SIZE) + || !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; + } + + local_irq_save(flags); + + ep->bEndpointAddress = desc->bEndpointAddress; + ep->bmAttributes = desc->bmAttributes; + ep->enabled = 1; + ep->got_token = 0; + ep->stopped = 0; + ep->pio_irqs = 0; + ep->desc = desc; + ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize); + + if (ep_is_in(ep)) { + s3c_otg_ep_control(ep_index(ep), USB_DIR_IN, ep_fifo_size, 1); + if (desc->bmAttributes == USB_ENDPOINT_XFER_BULK) { + ep->ep_type = ep_bulk_in; + /* assume that BULK endpoints always have tokens */ + ep->got_token = 1; + s3c_otg_ep_control(ep_index(ep), USB_DIR_IN, + DEPCTL_BULK_TYPE|DEPCTL_USBACTEP, 1); + } else if (desc->bmAttributes == USB_ENDPOINT_XFER_INT) { + ep->ep_type = ep_interrupt; + s3c_otg_ep_control(ep_index(ep), USB_DIR_IN, + DEPCTL_INTR_TYPE|DEPCTL_USBACTEP, 1); + } + + /* Unmask EPx interrupt */ + s3c_otg_set_ep_int_mask(ep_index(ep), USB_DIR_IN, 1); + } else { + s3c_otg_ep_control(ep_index(ep), USB_DIR_OUT, ep_fifo_size, 1); + if (desc->bmAttributes == USB_ENDPOINT_XFER_BULK) { + ep->ep_type = ep_bulk_out; + s3c_otg_ep_control(ep_index(ep), USB_DIR_OUT, + DEPCTL_EPDIS| + DEPCTL_BULK_TYPE|DEPCTL_USBACTEP, 1); + } else if (desc->bmAttributes == USB_ENDPOINT_XFER_INT) { + ep->ep_type = ep_interrupt; + s3c_otg_ep_control(ep_index(ep), USB_DIR_OUT, + DEPCTL_EPDIS| + DEPCTL_INTR_TYPE|DEPCTL_USBACTEP, 1); + } + } + + /* Reset halt state */ + s3c_otg_set_halt(_ep, 0); + + local_irq_restore(flags); + + DBG(2, "enabled %s, stopped = %d, maxpacket = %d\n", + _ep->name, ep->stopped, ep->ep.maxpacket); + + return 0; +} + +/* + * Disable endpoint, clear it parameters + */ +static int s3c_otg_ep_disable(struct usb_ep *_ep) +{ + struct s3c_ep *ep; + unsigned long flags; + u32 epctrl; + u32 epctrlValue; + u32 ep_num; + + 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; + } + + local_irq_save(flags); + + /* Nuke all pending requests */ + s3c_otg_nuke(ep, -ESHUTDOWN); + + /* Set end point as not active and clear EPx interrupt mask bits */ + ep_num = ep_index(ep); + + s3c_otg_set_ep_int_mask(ep_num, ep_is_in(ep), 0); + + if (ep_is_in(ep)) + epctrl = (S3C_UDC_OTG_DIEPCTL(ep_num)); + else + epctrl = (S3C_UDC_OTG_DOEPCTL(ep_num)); + + epctrlValue = s3c_otg_readl(epctrl); + epctrlValue &= ~DEPCTL_USBACTEP; + s3c_otg_writel(epctrlValue, epctrl); + + ep->desc = 0; + ep->stopped = 1; + ep->enabled = 0; + ep->got_token = 0; + ep->ep.maxpacket = EP_FIFO_SIZE; + + local_irq_restore(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_speed(struct s3c_udc *dev, enum usb_device_speed speed) +{ + u32 ep0_mps; + u8 i; + + if (speed == USB_SPEED_HIGH) { + ep0_fifo_size = EP0_FIFO_SIZE; + ep_fifo_size = EP_FIFO_SIZE; + ep_fifo_size2 = EP_FIFO_SIZE2; + ep0_mps = DEPCTL0_MPS_64; + } else { /* low speed */ + 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; + + for (i = 1; i < S3C_MAX_ENDPOINTS; i++) + dev->ep[i].ep.maxpacket = ep_fifo_size; + + /* EP0 - Control */ + s3c_otg_ep_control(0, USB_DIR_OUT, ep0_mps, 1); + s3c_otg_ep_control(0, USB_DIR_IN, ep0_mps, 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) +{ + u32 dcfg = s3c_otg_readl(S3C_UDC_OTG_DCFG); + dcfg &= ~DEVICE_ADDR(0x7F); + s3c_otg_writel(DEVICE_ADDR(addr) | dcfg, S3C_UDC_OTG_DCFG); + + 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; +} + +/* + * read setup request from rx fifo + */ +static inline int s3c_otg_read_setup(struct s3c_ep *ep, u32 *ctrl, int max) +{ + int bytes; + int count; + u32 csr = s3c_otg_readl(S3C_UDC_OTG_GRXSTSP); + + bytes = BYTE_COUNT(csr); + + /* 32 bits interface */ + count = bytes / 4; + + while (count--) + *ctrl++ = s3c_otg_readl(S3C_UDC_OTG_EP_FIFO(0)); + + return bytes; +} + +/* + * An answer to a setup packet + */ +static void s3c_otg_setup(struct s3c_udc *dev) +{ + struct s3c_ep *ep = &dev->ep[0]; + int bytes; + int is_in; + int non_std_req = 0; + 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); + + 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); + non_std_req = 1; + break; + } + + if (dev->driver) { + /* device-2-host (IN) or no data setup command, + * process immediately */ + + DBG(1, "usb_ctrlrequest will be passed to fsg_setup()\n"); + + ret = dev->driver->setup(&dev->gadget, &ctrl); + + if (ret < 0) { + /* setup processing failed, force stall */ + DBG(3, "gadget setup FAILED (stalling) - %d\n", ret); + dev->ep0state = WAIT_FOR_SETUP; + } + } + + if (ret >= 0 && is_in == 0 && non_std_req) { + /* send ZLP to acknowledle OUT interface control request */ + DBG(2, "sending ZLP to acknowledle OUT control request: %d\n", is_in); + s3c_otg_ep_control(0, USB_DIR_IN, DEPCTL_EPENA|DEPCTL_CNAK, 1); + } +} + +/* + * handle ep0 interrupt + */ +static void s3c_otg_handle_ep0(struct s3c_udc *dev) +{ + if (dev->ep0state == WAIT_FOR_SETUP) + s3c_otg_setup(dev); + else { + struct s3c_request *req; + struct s3c_ep *ep = &dev->ep[0]; + DBG(3, "strange state: %s, expected WAIT_FOR_SETUP\n", state_names[dev->ep0state]); + + /* kill the request... */ + if (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, struct s3c_request, queue); + s3c_otg_done(ep, req, -EPROTO); + } + dev->ep0state = WAIT_FOR_SETUP; + } +} + +/* + * start reading OUT packet for specified endpoint + */ +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); +} + +/* + * start writing packet to FIFO for specified endpoint + */ +static int 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 0; + } else { + int res = s3c_otg_write_fifo(ep, req); + return res; + } + return 0; +} + +/* + * find a waiting packet and start writing an IN packet to common NPTX FIFO + */ +static int s3c_otg_handle_tx_fifo(struct s3c_udc *dev) +{ + int write_done = 0; + struct s3c_ep *ep; + u8 i; + + if (likely(!tx_stopped)) { + if (current_ep == NO_EP_ACTIVE) { + /* send interrupt packets first... */ + for (i = 1; i < S3C_MAX_ENDPOINTS && !write_done; i++) { + ep = &dev->ep[i]; + if (ep_is_in(ep) && ep->got_token && ep->ep_type == ep_interrupt) { + if (!list_empty(&ep->queue)) + write_done = s3c_otg_handle_ep_in(dev, i); + } + } + /* ... then bulk ones */ + for (i = 1; i < S3C_MAX_ENDPOINTS && !write_done; i++) { + ep = &dev->ep[i]; + if (ep_is_in(ep) && ep->got_token && ep->ep_type != ep_interrupt) { + if (!list_empty(&ep->queue)) + write_done = s3c_otg_handle_ep_in(dev, i); + } + } + } else { + ep = &dev->ep[current_ep]; + if (!list_empty(&ep->queue)) + write_done = s3c_otg_handle_ep_in(dev, current_ep); + } + } + + if (!write_done) { + s3c_otg_set_int_mask(INT_TX_FIFO_EMPTY, 0); + DBG(2, "no requests, -> disabling INT_TX_FIFO_EMPTY\n"); + } + return write_done; +} + +/* + * handle RX FIFO not empty interrupt + */ +static void s3c_otg_handle_rx_fifo(struct s3c_udc *dev) +{ + u32 csr; + u32 packet_status; + u32 ep_num; + u32 bytes = 0; + + s3c_otg_set_int_mask(INT_RX_FIFO_NOT_EMPTY, 0); + DBG(2, "INT_RX_FIFO_NOT_EMPTY disabled\n"); + + csr = s3c_otg_readl(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; + + /* make sure that tx fifo is empty to correctly send SETUP packet reply */ + tx_stopped = 1; + + DBG(2, "GNPTXSTS reg: %08x\n", s3c_otg_readl(S3C_UDC_OTG_GNPTXSTS)); + if (((s3c_otg_readl(S3C_UDC_OTG_GNPTXSTS) & GNPTXSTS_NPTXQSPCAVAIL) >> 16) != 8) { + DBG(2, "warning - tx fifo not empty while processing setup req!\n"); + /* Yet another ugly HACK: make sure that tx fifo + WILL be empty when processing setup packet req */ + mdelay(1); /* FIXME: check if 1ms is enough/too long */ + } + s3c_otg_handle_ep0(dev); + s3c_otg_set_int_mask(INT_RX_FIFO_NOT_EMPTY, 1); + DBG(2, "INT_RX_FIFO_NOT_EMPTY enabled\n"); + break; + + case OUT_PKT_RECEIVED: + if (!bytes) { + DBG(2, "ZERO OUT packet received\n"); + break; + } + + if (ep_num == 0) { + DBG(2, "CONTROL OUT received : %d bytes\n", bytes); + + dev->ep0state = DATA_STATE_RECV; + s3c_otg_read_ep0(dev); + + s3c_otg_set_int_mask(INT_RX_FIFO_NOT_EMPTY, 1); + DBG(2, "INT_RX_FIFO_NOT_EMPTY enabled\n"); + + } else { + DBG(2, " Bulk OUT or INT OUT received : %d bytes\n", bytes); + + s3c_otg_handle_ep_out(dev, ep_num); + + if (!list_empty(&dev->ep[ep_num].queue)) + s3c_otg_ep_control(ep_num, USB_DIR_OUT, DEPCTL_CNAK, 1); + } + break; + + case SETUP_COMPLETED: + DBG(2, "SETUP_COMPLETED\n"); + s3c_otg_ep_control(0, USB_DIR_OUT, DEPCTL_CNAK, 1); + + /* enable sending any IN packets, gadget->setup might queue some */ + tx_stopped = 0; + s3c_otg_handle_tx_fifo(dev); + 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: + s3c_otg_set_int_mask(INT_RX_FIFO_NOT_EMPTY, 1); + DBG(2, "INT_RX_FIFO_NOT_EMPTY enabled\n"); + + DBG(2, "reserved packet received : %d bytes\n", bytes); + break; + } + + if (!bytes) { + /* pop fifo */ + csr = s3c_otg_readl(S3C_UDC_OTG_GRXSTSP); + DBG(2, "RX Fifo poped\n"); + s3c_otg_set_int_mask(INT_RX_FIFO_NOT_EMPTY, 1); + DBG(2, "INT_RX_FIFO_NOT_EMPTY enabled\n"); + } +} + +/* + * 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; + + writel(SRC_PHYPWR_ANALOG_POWERDOWN, S3C_PHYPWR); + dev->usb_enabled = 0; +} + +/* + * initialize software state + */ +static void s3c_otg_reinit(struct s3c_udc *dev) +{ + u32 i; + + /* end point structures */ + memset(dev->ep, 0, sizeof(dev->ep)); + memset(dev->ep_names, 0, sizeof(dev->ep_names)); + + /* device/ep0 records init */ + INIT_LIST_HEAD(&dev->gadget.ep_list); + INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); + + /* basic endpoint records init */ + for (i = 0; i < S3C_MAX_ENDPOINTS; i++) { + struct s3c_ep *ep = &dev->ep[i]; + sprintf(dev->ep_names[i], "ep%d", i); + ep->ep.name = dev->ep_names[i]; + ep->ep.ops = &s3c_ep_ops; + ep->ep.maxpacket = EP_FIFO_SIZE; + ep->dev = dev; + ep->fifo = S3C_UDC_OTG_EP_FIFO(i); + + INIT_LIST_HEAD(&ep->queue); + if (i != 0) + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + } + + dev->ep[0].ep.name = ep0name; + dev->ep[0].ep.maxpacket = EP0_FIFO_SIZE; + dev->ep[0].ep_type = ep_control; + dev->ep[0].enabled = 1; + + dev->ep0state = WAIT_FOR_SETUP; +} + +#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 | INT_OUT_EP | INT_IN_EP) + +#define DOEPMSK_INIT (AHB_ERROR|TRANSFER_DONE) + +#define DIEPMSK_INIT (IN_EP_TIMEOUT|AHB_ERROR|IN_TK_TXFEMP|TRANSFER_DONE) + +#define GAHBCFG_INIT (PTXFE_HALF|NPTXFE_HALF| \ + MODE_SLAVE|BURST_INCR16|GBL_INT_UNMASK) + +/* + * Set configuration registers + */ +static void s3c_otg_config(void) +{ + u32 reg; + u8 i; + + /* OTG USB configuration */ + s3c_otg_writel(GUSBCFG_INIT, S3C_UDC_OTG_GUSBCFG); + + /* Soft-reset OTG Core and then unreset again */ + s3c_otg_writel(CORE_SOFT_RESET, S3C_UDC_OTG_GRSTCTL); + + /* Put the OTG device core in the disconnected state */ + reg = s3c_otg_readl(S3C_UDC_OTG_DCTL); + s3c_otg_writel(reg | SOFT_DISCONNECT, S3C_UDC_OTG_DCTL); + + udelay(20); + + /* Make the OTG device core exit from the disconnected state */ + reg = s3c_otg_readl(S3C_UDC_OTG_DCTL); + s3c_otg_writel(reg & ~SOFT_DISCONNECT, S3C_UDC_OTG_DCTL); + + /* Configure OTG Core to initial settings of device mode */ + s3c_otg_writel(EP_MIS_CNT(0x1)|SPEED_2_HIGH, S3C_UDC_OTG_DCFG); + + udelay(1000); + + /* Set Rx FIFO Size */ + s3c_otg_writel(RX_FIFO_SIZE, S3C_UDC_OTG_GRXFSIZ); + + /* Set Non Periodic Tx FIFO Size */ + s3c_otg_writel(NPTX_FIFO_SIZE|NPTX_FIFO_START_ADDR, S3C_UDC_OTG_GNPTXFSIZ); + + /* FIFO status */ + DBG(2, "Fifo status: RXFSIZ: %08x\n", + s3c_otg_readl(S3C_UDC_OTG_GRXFSIZ)); + DBG(2, "Fifo status: NPTXFSIZ: %08x\n", + s3c_otg_readl(S3C_UDC_OTG_GNPTXFSIZ)); + + /* Unmask the core interrupts */ + s3c_otg_writel(GINTMSK_INIT, S3C_UDC_OTG_GINTMSK); + + + /* 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); + + for (i = 1; i < S3C_MAX_ENDPOINTS; i++) { + s3c_otg_ep_control(i, USB_DIR_OUT, + DEPCTL_EPDIS|DEPCTL_SNAK|DEPCTL_BULK_TYPE, 0); + s3c_otg_ep_control(i, USB_DIR_IN, + DEPCTL_EPDIS|DEPCTL_SNAK|DEPCTL_BULK_TYPE, 0); + } + + /* Unmask device OUT EP common interrupts */ + s3c_otg_writel(DOEPMSK_INIT, S3C_UDC_OTG_DOEPMSK); + + /* Unmask device IN EP common interrupts */ + s3c_otg_writel(DIEPMSK_INIT, S3C_UDC_OTG_DIEPMSK); + + /* 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, S3C_UDC_OTG_GAHBCFG); +} + +/* + * Enable OTG USB hardware + */ +static int s3c_otg_enable(struct s3c_udc *dev) +{ + u32 others; + + /* USB_SIG_MASK */ + others = readl(S3C64XX_OTHERS); + writel(S3C64XX_OTHERS_USBMASK | others, S3C64XX_OTHERS); + + /* Initializes OTG Phy. */ + writel(0, S3C_PHYPWR); + writel(dev->phyclk, S3C_PHYCLK); + + writel(S3C_RSTCON_PHY, S3C_RSTCON); + udelay(50); + + writel(0, S3C_RSTCON); + udelay(50); + + s3c_otg_config(); + + DBG(2, "S3C USB 2.0 OTG Controller Core Initialized\n"); + + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->usb_enabled = 1; + + 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; + unsigned long flags; + + local_irq_save(flags); + + intr_status = s3c_otg_readl(S3C_UDC_OTG_GINTSTS); + gintmsk = s3c_otg_readl(S3C_UDC_OTG_GINTMSK); + + DBG(1, "\n"); + DBG(1, "GINTSTS=0x%08x(on state %s), GINTMSK : 0x%08x\n", + intr_status, state_names[dev->ep0state], gintmsk); + + if (intr_status & INT_IN_EP) { + int i; + u32 daint = s3c_otg_readl(S3C_UDC_OTG_DAINT); + u32 mask = s3c_otg_readl(S3C_UDC_OTG_DIEPMSK); + DBG(2, "IN Endpoint int: %08x, in ep mask: %08x\n", daint, mask); + + for (i = 1; i < S3C_MAX_ENDPOINTS; i++) + if (daint & S3C_UDC_INT_IN_EP(i)) { + u32 sts = s3c_otg_readl(S3C_UDC_OTG_DIEPINT(i)); + DBG(2, "\tep %d, DIEPINT: %08x\n", i, sts & mask); + + if (sts & TRANSFER_DONE) { /* pkt complete */ + DBG(2, "\tep %d, packet completed\n", i); + } + if (sts & IN_TK_TXFEMP) { /* token received ! */ + DBG(2, "\tep %d, IN token received!\n", i); + if (i > 0) + s3c_otg_handle_in_token(dev, i); + } + s3c_otg_writel(sts, S3C_UDC_OTG_DIEPINT(i)); + } + } + +#if 0 + if (intr_status & INT_OUT_EP) { + int i; + u32 daint = s3c_otg_readl(S3C_UDC_OTG_DAINT); + u32 mask = s3c_otg_readl(S3C_UDC_OTG_DOEPMSK); + DBG(2, "OUT Endpoint int: %08x, out ep mask: %08x\n", daint, mask); + + for (i = 1; i < S3C_MAX_ENDPOINTS; i++) + if (daint & S3C_UDC_INT_OUT_EP(i)) { + u32 sts = s3c_otg_readl(S3C_UDC_OTG_DOEPINT(i)); + DBG(2, "\tep %d, DOEPINT %08x\n", i, sts); + + if (sts & TRANSFER_DONE) + DBG(2, "\tep %d, packet completed\n", i); + + s3c_otg_writel(sts, S3C_UDC_OTG_DOEPINT(i)); + } + } +#endif + + if (intr_status & INT_ENUMDONE) { + DBG(2, "Speed Detection interrupt\n"); + s3c_otg_writel(INT_ENUMDONE, S3C_UDC_OTG_GINTSTS); + + usb_status = ENUM_SPEED(s3c_otg_readl(S3C_UDC_OTG_DSTS)); + + if (usb_status & (USB_FULL_30_60MHZ | USB_FULL_48MHZ)) + s3c_otg_set_speed(dev, USB_SPEED_FULL); + else + s3c_otg_set_speed(dev, USB_SPEED_HIGH); + } + + if (intr_status & INT_EARLY_SUSPEND) { + DBG(2, "Early suspend interrupt\n"); + s3c_otg_writel(INT_EARLY_SUSPEND, S3C_UDC_OTG_GINTSTS); + } + + if (intr_status & INT_SUSPEND) { + DBG(2, "Suspend interrupt\n"); + s3c_otg_writel(INT_SUSPEND, S3C_UDC_OTG_GINTSTS); + + 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, S3C_UDC_OTG_GINTSTS); + + 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, S3C_UDC_OTG_GINTSTS); + + usb_status = s3c_otg_readl(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) { + DBG(2, " ** INT_RX_FIFO_NOT_EMPTY\n"); + s3c_otg_handle_rx_fifo(dev); + } + + + if ((intr_status & INT_TX_FIFO_EMPTY) && (gintmsk & INT_TX_FIFO_EMPTY)) { + DBG(2, " ** INT_TX_FIFO_EMPTY\n"); + s3c_otg_handle_tx_fifo(dev); + } + + local_irq_restore(flags); + return IRQ_HANDLED; +} + +/* + * Stop all activity and prepare to shutdown + */ +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) + driver->disconnect(&dev->gadget); + + /* re-init driver-visible data structures */ + s3c_otg_reinit(dev); +} + +/* + * Usb detect interrupt handler + */ +static irqreturn_t s3c_otg_usb_detect_irq(int irq, void *_dev) +{ + struct s3c_udc *dev = _dev; + int val; + + mdelay(1); + val = gpio_get_value(dev->usb_detect_gpio); + + if (val != dev->usb_detected) { + + unsigned long flags; + local_irq_save(flags); + + dev->usb_detected = val; + + if (val) { + DBG(3, "USB cable attached\n"); + if (dev->usb_enabled) + s3c_otg_enable(dev); + } else { + DBG(3, "USB cable detached\n"); + if (dev->usb_enabled && dev->driver) + s3c_otg_stop_activity(dev, dev->driver); + } + local_irq_restore(flags); + } + + return IRQ_HANDLED; +} + +/* + * 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; + unsigned long flags; + + if (!driver + || driver->speed != USB_SPEED_HIGH + || !driver->bind + || !driver->setup) + return -EINVAL; + + if (!dev) { + DBG(3, "No device\n"); + return -ENODEV; + } + + spin_lock_irqsave(&dev->lock, flags); + + if (dev->driver) { + DBG(3, "Already bound to %s\n", driver->driver.name); + spin_unlock_irqrestore(&dev->lock, flags); + return -EBUSY; + } + + /* first hook up the driver ... */ + dev->driver = driver; + dev->gadget.dev.driver = &driver->driver; + + spin_unlock_irqrestore(&dev->lock, flags); + + 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(dev->irq); + + 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; + + disable_irq(dev->irq); + + 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); + + 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(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_name(dev)); +} + +static struct s3c_udc memory = { + .usb_address = 0, + .gadget = { + .ops = &s3c_udc_ops, + .ep0 = &memory.ep[0].ep, + .name = driver_name, + .dev = { + .init_name = "gadget", + .release = nop_release, + }, + }, +}; + +/* + * Setup an usb cable detect interrupt handler + * since otg chip cannot properly detect cable chagne, we use + * an external gpio interrupt for this purpose... + */ +static int s3c_otg_setup_usb_detect(struct s3c_udc *dev, int gpio_usb_detect) +{ + int retval, irq; + + retval = gpio_request(gpio_usb_detect, "usb_detect"); + if (retval < 0) { + DBG(3, "%s: can't request gpio %d, error %d !!!\n", + driver_name, gpio_usb_detect, retval); + return -EBUSY; + } + + retval = gpio_direction_input(gpio_usb_detect); + if (retval < 0) { + DBG(3, "%s: can't configure gpio %d as input, error %d !!!\n", + driver_name, gpio_usb_detect, retval); + gpio_free(dev->usb_detect_gpio); + return -EBUSY; + } + + irq = gpio_to_irq(gpio_usb_detect); + if (irq < 0) { + DBG(3, "%s: can't configure gpio %d as interrupt, error %d !!!\n", + driver_name, gpio_usb_detect, retval); + gpio_free(dev->usb_detect_gpio); + return -EBUSY; + } + + retval = request_irq(irq, s3c_otg_usb_detect_irq, + IRQF_SHARED|IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING, + driver_name, dev); + + if (retval != 0) { + DBG(3, "%s: can't get usb detect irq - %d\n", driver_name, retval); + gpio_free(dev->usb_detect_gpio); + return -EBUSY; + } + + dev->usb_detect_irq = irq; + dev->usb_detect_gpio = gpio_usb_detect; + dev->has_usb_detect = 1; + dev->usb_detected = gpio_get_value(dev->usb_detect_gpio); + return 0; +} + +/* + * binds to the platform device + */ +static int s3c_otg_probe(struct platform_device *pdev) +{ + struct s3c_udc *dev = &memory; + struct s3c_hsotg_plat *pdata = pdev->dev.platform_data; + struct resource *res; + 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 = 0; + if (pdata && pdata->is_osc) + dev->phyclk = S3C_PHYCLK_EXT_OSC; + + the_controller = dev; + platform_set_drvdata(pdev, dev); + + dev->otg_clock = clk_get(&pdev->dev, "otg"); + if (dev->otg_clock == NULL) { + DBG(3, "failed to find otg clock source\n"); + return -ENOENT; + } + clk_enable(dev->otg_clock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + DBG(3, "cannot find register resource 0\n"); + return -EINVAL; + } + + dev->regs_res = request_mem_region(res->start, resource_size(res), + dev_name(&pdev->dev)); + if (!dev->regs_res) { + DBG(3, "cannot reserve registers\n"); + return -ENOENT; + } + + dev->regs = ioremap(res->start, resource_size(res)); + if (!dev->regs) { + DBG(3, "cannot map registers\n"); + retval = -ENXIO; + goto err_io; + } + + s3c_otg_reinit(dev); + + dev->irq = platform_get_irq(pdev, 0); + if (dev->irq < 0) { + DBG(3, "cannot find IRQ\n"); + retval = dev->irq; + goto err_irq; + } + + /* irq setup after old hardware state is cleaned up */ + retval = request_irq(dev->irq, s3c_otg_irq, + IRQF_DISABLED, driver_name, dev); + if (retval != 0) { + DBG(3, "%s: can't get irq %i - %d\n", + driver_name, dev->irq, retval); + retval = -EBUSY; + goto err_irq; + } + + if (pdata && pdata->gpio_usb_detect) { + retval = s3c_otg_setup_usb_detect(dev, pdata->gpio_usb_detect); + if (retval) + goto err_irq; + } + + disable_irq(dev->irq); + + create_proc_files(); + + return retval; + +err_irq: + iounmap(dev->regs); +err_io: + release_resource(dev->regs_res); + kfree(dev->regs_res); + + return retval; +} + +static int s3c_otg_remove(struct platform_device *pdev) +{ + struct s3c_udc *dev = platform_get_drvdata(pdev); + + if (dev->otg_clock != NULL) { + clk_disable(dev->otg_clock); + clk_put(dev->otg_clock); + dev->otg_clock = NULL; + } + + remove_proc_files(); + usb_gadget_unregister_driver(dev->driver); + + free_irq(dev->irq, dev); + iounmap(dev->regs); + + release_resource(dev->regs_res); + kfree(dev->regs_res); + + if (dev->has_usb_detect) { + free_irq(dev->usb_detect_irq, dev); + gpio_free(dev->usb_detect_gpio); + } + + 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(dev->irq); + s3c_otg_stop_activity(dev, dev->driver); + s3c_otg_disable(dev); + clk_disable(dev->otg_clock); + } + + return 0; +} + +static int s3c_otg_resume(struct platform_device *pdev) +{ + struct s3c_udc *dev = the_controller; + + if (dev->has_usb_detect) + dev->usb_detected = gpio_get_value(dev->usb_detect_gpio); + + if (dev->driver) { + clk_enable(dev->otg_clock); + s3c_otg_reinit(dev); + s3c_otg_enable(dev); + enable_irq(dev->irq); + } + + 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-hsotg", + }, +}; + +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"); diff --git a/drivers/usb/gadget/s3c_hs_udc.h b/drivers/usb/gadget/s3c_hs_udc.h new file mode 100644 index 0000000..8695e2b --- /dev/null +++ b/drivers/usb/gadget/s3c_hs_udc.h @@ -0,0 +1,153 @@ +/* + * drivers/usb/gadget/s3c_hs_udc.h + * Samsung S3C on-chip full/high speed USB device controllers + * + * Copyright (C) 2008-2009 Samsung Electronics + * Minkyu Kang <mk7.kang@xxxxxxxxxxx> + * Michal Lewandowski <m.lewandowski@xxxxxxxxxxx> + * Marek Szyprowski <m.szyprowski@xxxxxxxxxxx> + * + * 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 <linux/module.h> +#include <linux/kernel.h> +#include <linux/ioport.h> +#include <linux/types.h> +#include <linux/version.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/proc_fs.h> +#include <linux/mm.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <asm/byteorder.h> +#include <linux/io.h> +#include <asm/dma.h> +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/unaligned.h> +#include <mach/hardware.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +/* 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 + +#define NO_EP_ACTIVE -1 + +#define MAX_EP_NAMELEN 8 /* space for "epXX" string */ + +enum ep_type { + ep_none, 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; + + unsigned enabled : 1; + unsigned got_token : 1; + + 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; + struct clk *otg_clock; + + void __iomem *regs; + struct resource *regs_res; + int irq; + + int phyclk; + int ep0state; + struct s3c_ep ep[S3C_MAX_ENDPOINTS]; + char ep_names[S3C_MAX_ENDPOINTS][MAX_EP_NAMELEN]; + + unsigned char usb_address; + + unsigned req_pending:1; + unsigned req_std:1; + unsigned req_config:1; + + unsigned int usb_detect_gpio; + unsigned int usb_detect_irq; + + unsigned has_usb_detect:1; + unsigned usb_enabled:1; + unsigned usb_detected: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 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html