[U-Boot] [PATCH v4 1/8] h2200: Add support for iPAQ h2200 palmtop

Add basic support for HP iPAQ h2200 palmtop. h2200 palmtop was targeted to general consumers. It has 64 MB of RAM, 32 MB flash. No intergrated Wi-Fi nor Ethernet. Based on Intel PXA255 processor. It was shipped with Windows CE 4.2 operating system.
Signed-off-by: Lukasz Dalek luk0104@gmail.com --- board/h2200/Makefile | 51 ++++++++++++++ board/h2200/h2200-header.S | 27 ++++++++ board/h2200/h2200.c | 53 +++++++++++++++ boards.cfg | 1 + include/configs/h2200.h | 156 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 288 insertions(+), 0 deletions(-) create mode 100644 board/h2200/Makefile create mode 100644 board/h2200/h2200-header.S create mode 100644 board/h2200/h2200.c create mode 100644 include/configs/h2200.h
diff --git a/board/h2200/Makefile b/board/h2200/Makefile new file mode 100644 index 0000000..2265aa9 --- /dev/null +++ b/board/h2200/Makefile @@ -0,0 +1,51 @@ +# +# h2200 Support +# +# Copyright (C) 2012 Lukasz Dalek luk0104@gmail.com +# +# 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 $(TOPDIR)/config.mk + +LIB = $(obj)lib$(BOARD).o + +COBJS := h2200.o + +SRCS := $(COBJS:.o=.c) h2200-header.S +OBJS := $(addprefix $(obj),$(COBJS)) + +ALL: $(LIB) h2200-header.bin + +h2200-header.o: h2200-header.S + $(CC) $(CFLAGS) -c -o $@ $< + +h2200-header.bin: h2200-header.o + $(OBJCOPY) -O binary $< $@ + +all: $(LIB) h2200-header.bin + +$(LIB): $(obj).depend $(OBJS) + $(call cmd_link_o_target, $(OBJS)) + +######################################################################### + +# defines $(obj).depend target +include $(SRCTREE)/rules.mk + +sinclude $(obj).depend + +######################################################################### diff --git a/board/h2200/h2200-header.S b/board/h2200/h2200-header.S new file mode 100644 index 0000000..c335bfe --- /dev/null +++ b/board/h2200/h2200-header.S @@ -0,0 +1,27 @@ +/* + * iPAQ h2200 header + * + * Copyright (C) 2012 Lukasz Dalek luk0104@gmail.com + * + * 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 + */ + + .word 0xea0003fe /* b 0x1000 */ + + .org 0x40 + .ascii "ECEC" + + .org 0x1000 - 1 + .byte 0x0 diff --git a/board/h2200/h2200.c b/board/h2200/h2200.c new file mode 100644 index 0000000..3076306 --- /dev/null +++ b/board/h2200/h2200.c @@ -0,0 +1,53 @@ +/* + * iPAQ h2200 board configuration + * + * Copyright (C) 2012 Lukasz Dalek luk0104@gmail.com + * + * 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 <common.h> +#include <asm/arch/pxa.h> +#include <asm/arch/pxa-regs.h> +#include <asm/io.h> + +DECLARE_GLOBAL_DATA_PTR; + +int board_init(void) +{ + /* We have RAM, disable cache */ + dcache_disable(); + icache_disable(); + + gd->bd->bi_arch_number = MACH_TYPE_H2200; + + /* adress of boot parameters */ + gd->bd->bi_boot_params = 0xa0000100; + + return 0; +} + +int dram_init(void) +{ + /* + * Everything except MSC0 was already set up by + * 1st stage bootloader. + * + * This setting enables access to companion chip. + */ + clrsetbits_le32(MSC0, 0xffffffff, CONFIG_SYS_MSC0_VAL); + gd->ram_size = CONFIG_SYS_SDRAM_SIZE; + return 0; +} diff --git a/boards.cfg b/boards.cfg index b4e0d3c..ab10938 100644 --- a/boards.cfg +++ b/boards.cfg @@ -282,6 +282,7 @@ dvlhost arm ixp pdnb3 arm ixp pdnb3 prodrive scpu arm ixp pdnb3 prodrive - pdnb3:SCPU balloon3 arm pxa +h2200 arm pxa lubbock arm pxa palmld arm pxa palmtc arm pxa diff --git a/include/configs/h2200.h b/include/configs/h2200.h new file mode 100644 index 0000000..de5299b --- /dev/null +++ b/include/configs/h2200.h @@ -0,0 +1,156 @@ +/* + * iPAQ h2200 board configuration + * + * Copyright (C) 2012 Lukasz Dalek luk0104@gmail.com + * + * 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 __CONFIG_H +#define __CONFIG_H + +#define MACH_TYPE_H2200 341 +#define CONFIG_MACH_TYPE MACH_TYPE_H2200 + +#define CONFIG_CPU_PXA25X 1 +#define CONFIG_BOARD_H2200 + +#define CONFIG_SYS_NO_FLASH + +#define CONFIG_SYS_HZ 1000 + +#define CONFIG_NR_DRAM_BANKS 1 +#define PHYS_SDRAM_1 0xa0000000 /* SDRAM Bank #1 */ +#define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */ + +#define CONFIG_SYS_SDRAM_BASE PHYS_SDRAM_1 +#define CONFIG_SYS_SDRAM_SIZE PHYS_SDRAM_1_SIZE + +#define CONFIG_SYS_INIT_SP_ADDR 0xfffff800 + +#define CONFIG_ENV_SIZE 0x00040000 +#define CONFIG_SYS_MALLOC_LEN (CONFIG_ENV_SIZE + 128*1024) + +#define CONFIG_ENV_IS_NOWHERE +#define CONFIG_SYS_MAXARGS 16 +#define CONFIG_SYS_LOAD_ADDR 0xa3000000 /* default load address */ + +/* + * iPAQ 1st stage bootloader loads 2nd stage bootloader + * at address 0xa0040000 but bootloader requires header + * which is 0x1000 long. + * + * --- Header begin --- + * .word 0xea0003fe ; b 0x1000 + * + * .org 0x40 + * .ascii "ECEC" + * + * .org 0x1000 + * --- Header end --- + */ + +#define CONFIG_SYS_TEXT_BASE 0xa0041000 + +/* + * Static chips + */ + +#define CONFIG_SYS_MSC0_VAL 0x246c7ffc +#define CONFIG_SYS_MSC1_VAL 0x7ff07ff0 +#define CONFIG_SYS_MSC2_VAL 0x7ff07ff0 + +/* + * PCMCIA and CF Interfaces + */ + +#define CONFIG_SYS_MECR_VAL 0x00000000 +#define CONFIG_SYS_MCMEM0_VAL 0x00000000 +#define CONFIG_SYS_MCMEM1_VAL 0x00000000 +#define CONFIG_SYS_MCATT0_VAL 0x00000000 +#define CONFIG_SYS_MCATT1_VAL 0x00000000 +#define CONFIG_SYS_MCIO0_VAL 0x00000000 +#define CONFIG_SYS_MCIO1_VAL 0x00000000 + +#define CONFIG_SYS_FLYCNFG_VAL 0x00000000 +#define CONFIG_SYS_SXCNFG_VAL 0x00040004 + +#define CONFIG_SYS_MDREFR_VAL 0x0099E018 +#define CONFIG_SYS_MDCNFG_VAL 0x01C801CB +#define CONFIG_SYS_MDMRS_VAL 0x00220022 + +#define CONFIG_SYS_PSSR_VAL 0x00000000 +#define CONFIG_SYS_CKEN 0x00004840 +#define CONFIG_SYS_CCCR 0x00000161 + +/* + * GPIOs + */ + +#define CONFIG_SYS_GPSR0_VAL 0x01000000 +#define CONFIG_SYS_GPSR1_VAL 0x00000000 +#define CONFIG_SYS_GPSR2_VAL 0x00010000 + +#define CONFIG_SYS_GPCR0_VAL 0x00000000 +#define CONFIG_SYS_GPCR1_VAL 0x00000000 +#define CONFIG_SYS_GPCR2_VAL 0x00000000 + +#define CONFIG_SYS_GPDR0_VAL 0xF7E38C00 +#define CONFIG_SYS_GPDR1_VAL 0xBCFFBF83 +#define CONFIG_SYS_GPDR2_VAL 0x000157FF + +#define CONFIG_SYS_GAFR0_L_VAL 0x80401000 +#define CONFIG_SYS_GAFR0_U_VAL 0x00000112 +#define CONFIG_SYS_GAFR1_L_VAL 0x600A9550 +#define CONFIG_SYS_GAFR1_U_VAL 0x0005AAAA +#define CONFIG_SYS_GAFR2_L_VAL 0x20000000 +#define CONFIG_SYS_GAFR2_U_VAL 0x00000000 + +/* + * Serial port + */ + +#define CONFIG_PXA_SERIAL +#define CONFIG_FFUART + +#define CONFIG_BAUDRATE 115200 +#define CONFIG_SYS_BAUDRATE_TABLE { 9600, 38400, 115200 } + +#define CONFIG_CMD_IMPORTENV 1 +#define CONFIG_CMD_LOADB +#define CONFIG_CMD_SOURCE +#define CONFIG_CMD_RUN +#define CONFIG_CMD_IMI + +#define CONFIG_FIT +#define CONFIG_SETUP_MEMORY_TAGS +#define CONFIG_CMDLINE_TAG +#define CONFIG_INITRD_TAG + +/* Monitor Command Prompt */ +#define CONFIG_SYS_PROMPT "> " +#define CONFIG_SYS_HUSH_PARSER +#define CONFIG_SYS_PROMPT_HUSH_PS2 "$ " + +/* Console I/O Buffer Size */ +#define CONFIG_SYS_CBSIZE 256 + +/* Print Buffer Size */ +#define CONFIG_SYS_PBSIZE (CONFIG_SYS_CBSIZE + \ + sizeof(CONFIG_SYS_PROMPT) + 16) + +#define CONFIG_BOOTARGS "root=/dev/ram0 ro console=ttyS0,115200n8" + +#endif /* __CONFIG_H */

Add function which return CPU model and revision which can be used for cpu detection.
Signed-off-by: Lukasz Dalek luk0104@gmail.com --- arch/arm/cpu/pxa/cpuinfo.c | 11 +++++++++-- arch/arm/include/asm/arch-pxa/pxa.h | 13 +++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/arch/arm/cpu/pxa/cpuinfo.c b/arch/arm/cpu/pxa/cpuinfo.c index f1cdd40..bab6340 100644 --- a/arch/arm/cpu/pxa/cpuinfo.c +++ b/arch/arm/cpu/pxa/cpuinfo.c @@ -24,9 +24,11 @@ #include <errno.h> #include <linux/compiler.h>
-#define CPU_MASK_PXA_REVID 0x00f +#define CPU_MASK_PXA_PRODID 0x000003f0 +#define CPU_MASK_PXA_REVID 0x0000000f + +#define CPU_MASK_PRODREV (CPU_MASK_PXA_PRODID | CPU_MASK_PXA_REVID)
-#define CPU_MASK_PXA_PRODID 0x3f0 #define CPU_VALUE_PXA25X 0x100 #define CPU_VALUE_PXA27X 0x110
@@ -51,6 +53,11 @@ int cpu_is_pxa27x(void) return id == CPU_VALUE_PXA27X; }
+uint32_t pxa_get_cpu_revision(void) +{ + return pxa_get_cpuid() & CPU_MASK_PRODREV; +} + #ifdef CONFIG_DISPLAY_CPUINFO static const char *pxa25x_get_revision(void) { diff --git a/arch/arm/include/asm/arch-pxa/pxa.h b/arch/arm/include/asm/arch-pxa/pxa.h index 49c6552..b67d8f2 100644 --- a/arch/arm/include/asm/arch-pxa/pxa.h +++ b/arch/arm/include/asm/arch-pxa/pxa.h @@ -22,8 +22,21 @@ #ifndef __PXA_H__ #define __PXA_H__
+#define PXA255_A0 0x00000106 +#define PXA250_C0 0x00000105 +#define PXA250_B2 0x00000104 +#define PXA250_B1 0x00000103 +#define PXA250_B0 0x00000102 +#define PXA250_A1 0x00000101 +#define PXA250_A0 0x00000100 +#define PXA210_C0 0x00000125 +#define PXA210_B2 0x00000124 +#define PXA210_B1 0x00000123 +#define PXA210_B0 0x00000122 + int cpu_is_pxa25x(void); int cpu_is_pxa27x(void); +uint32_t pxa_get_cpu_revision(void); void pxa2xx_dram_init(void);
#endif /* __PXA_H__ */

Dear Lukasz Dalek,
Add function which return CPU model and revision which can be used for cpu detection.
Signed-off-by: Lukasz Dalek luk0104@gmail.com
[...]
s/some stuff/code/ would be really nicer.
I think the rest of the patches are ok ... unless they changed?
Best regards, Marek Vasut

On 02.10.2012 21:36, Marek Vasut wrote:
Dear Lukasz Dalek,
Add function which return CPU model and revision which can be used for cpu detection.
Signed-off-by: Lukasz Dalekluk0104@gmail.com
[...]
s/some stuff/code/ would be really nicer.
I think the rest of the patches are ok ... unless they changed?
Nothing changed except patch 7/8 where I only added nicer comment
Łukasz Dałek

PXA25X chips don't support alternate settings so driver uses non-CDC driver. But only code defined between DEV_CONFIG_CDC signals that network is up. This patch is fixing this bug by signaling that network is up after USB SET_INTERFACE request.
Signed-off-by: Lukasz Dalek luk0104@gmail.com --- drivers/usb/gadget/ether.c | 17 ++++++++++++++++- 1 files changed, 16 insertions(+), 1 deletions(-)
diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index d975fb6..9ce2c17 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -44,7 +44,12 @@ extern struct platform_data brd;
unsigned packet_received, packet_sent;
-#define DEV_CONFIG_CDC 1 +#ifdef CONFIG_USB_GADGET_PXA2XX +# undef DEV_CONFIG_CDC +# define DEV_CONFIG_SUBSET 1 +#else +# define DEV_CONFIG_CDC 1 +#endif #define GFP_ATOMIC ((gfp_t) 0) #define GFP_KERNEL ((gfp_t) 0)
@@ -864,7 +869,9 @@ static struct usb_gadget_strings stringtab = {
/*============================================================================*/ static u8 control_req[USB_BUFSIZ]; +#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) static u8 status_req[STATUS_BYTECOUNT] __attribute__ ((aligned(4))); +#endif
/** @@ -1352,6 +1359,14 @@ eth_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) if (gadget_is_pxa(gadget)) { value = eth_set_config(dev, DEV_CONFIG_VALUE, GFP_ATOMIC); + /* + * PXA25x driver use non-CDC ethernet gadget. + * But only _CDC and _RNDIS code can signalize + * that network is working. So we signalize it + * here. + */ + l_ethdev.network_started = 1; + debug("USB network up!\n"); goto done_set_intf; }

Introduced CONFIG_USB_ETH_CDC and CONFIG_USB_ETH_SUBSET as preparation for removal DEV_CONFIG_CDC and DEV_CONFIG_SUBSET
Signed-off-by: Lukasz Dalek luk0104@gmail.com --- drivers/usb/gadget/ether.c | 19 ++++++++++++++----- 1 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index 9ce2c17..7e6491d 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -44,12 +44,21 @@ extern struct platform_data brd;
unsigned packet_received, packet_sent;
-#ifdef CONFIG_USB_GADGET_PXA2XX -# undef DEV_CONFIG_CDC -# define DEV_CONFIG_SUBSET 1 -#else -# define DEV_CONFIG_CDC 1 +#undef DEV_CONFIG_CDC +#undef DEV_CONFIG_SUBSET + +#if !defined(CONFIG_USB_ETH_CDC) && !defined(CONFIG_USB_ETH_SUBSET) +# define DEV_CONFIG_CDC 1 /* preserve default behavior */ +#endif + +#if defined(CONFIG_USB_ETH_CDC) +# define DEV_CONFIG_CDC 1 #endif + +#if defined(CONFIG_USB_ETH_SUBSET) +# define DEV_CONFIG_SUBSET 1 +#endif + #define GFP_ATOMIC ((gfp_t) 0) #define GFP_KERNEL ((gfp_t) 0)

Removed DEV_CONFIG_CDC and DEV_CONFIG_SUBSET and replaced it with CONFIG_USB_ETH_CDC and CONFIG_USB_ETH_SUBSET.
Signed-off-by: Lukasz Dalek luk0104@gmail.com --- drivers/usb/gadget/ether.c | 75 +++++++++++++++++-------------------------- 1 files changed, 30 insertions(+), 45 deletions(-)
diff --git a/drivers/usb/gadget/ether.c b/drivers/usb/gadget/ether.c index 7e6491d..1e187e5 100644 --- a/drivers/usb/gadget/ether.c +++ b/drivers/usb/gadget/ether.c @@ -44,21 +44,6 @@ extern struct platform_data brd;
unsigned packet_received, packet_sent;
-#undef DEV_CONFIG_CDC -#undef DEV_CONFIG_SUBSET - -#if !defined(CONFIG_USB_ETH_CDC) && !defined(CONFIG_USB_ETH_SUBSET) -# define DEV_CONFIG_CDC 1 /* preserve default behavior */ -#endif - -#if defined(CONFIG_USB_ETH_CDC) -# define DEV_CONFIG_CDC 1 -#endif - -#if defined(CONFIG_USB_ETH_SUBSET) -# define DEV_CONFIG_SUBSET 1 -#endif - #define GFP_ATOMIC ((gfp_t) 0) #define GFP_KERNEL ((gfp_t) 0)
@@ -174,9 +159,9 @@ static struct usb_gadget_driver eth_driver; /* "main" config is either CDC, or its simple subset */ static inline int is_cdc(struct eth_dev *dev) { -#if !defined(DEV_CONFIG_SUBSET) +#if !defined(CONFIG_USB_ETH_SUBSET) return 1; /* only cdc possible */ -#elif !defined(DEV_CONFIG_CDC) +#elif !defined(CONFIG_USB_ETH_CDC) return 0; /* only subset possible */ #else return dev->cdc; /* depends on what hardware we found */ @@ -420,7 +405,7 @@ rndis_config = { * get those drivers from MCCI, or bundled with various products. */
-#ifdef DEV_CONFIG_CDC +#ifdef CONFIG_USB_ETH_CDC static struct usb_interface_descriptor control_intf = { .bLength = sizeof control_intf, @@ -459,7 +444,7 @@ static const struct usb_cdc_header_desc header_desc = { .bcdCDC = __constant_cpu_to_le16(0x0110), };
-#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS)
static const struct usb_cdc_union_desc union_desc = { .bLength = sizeof union_desc, @@ -493,7 +478,7 @@ static const struct usb_cdc_acm_descriptor acm_descriptor = {
#endif
-#ifndef DEV_CONFIG_CDC +#ifndef CONFIG_USB_ETH_CDC
/* * "SAFE" loosely follows CDC WMC MDLM, violating the spec in various @@ -543,7 +528,7 @@ static const struct usb_cdc_ether_desc ether_desc = { .bNumberPowerFilters = 0, };
-#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS)
/* * include the status endpoint if we can, even where it's optional. @@ -575,7 +560,7 @@ fs_status_desc = { }; #endif
-#ifdef DEV_CONFIG_CDC +#ifdef CONFIG_USB_ETH_CDC
/* the default data interface has no endpoints ... */
@@ -630,7 +615,7 @@ rndis_data_intf = {
#endif
-#ifdef DEV_CONFIG_SUBSET +#ifdef CONFIG_USB_ETH_SUBSET
/* * "Simple" CDC-subset option is a simple vendor-neutral model that most @@ -676,7 +661,7 @@ fs_sink_desc = {
static const struct usb_descriptor_header *fs_eth_function[11] = { (struct usb_descriptor_header *) &otg_descriptor, -#ifdef DEV_CONFIG_CDC +#ifdef CONFIG_USB_ETH_CDC /* "cdc" mode descriptors */ (struct usb_descriptor_header *) &control_intf, (struct usb_descriptor_header *) &header_desc, @@ -690,12 +675,12 @@ static const struct usb_descriptor_header *fs_eth_function[11] = { (struct usb_descriptor_header *) &fs_source_desc, (struct usb_descriptor_header *) &fs_sink_desc, NULL, -#endif /* DEV_CONFIG_CDC */ +#endif /* CONFIG_USB_ETH_CDC */ };
static inline void fs_subset_descriptors(void) { -#ifdef DEV_CONFIG_SUBSET +#ifdef CONFIG_USB_ETH_SUBSET /* behavior is "CDC Subset"; extra descriptors say "SAFE" */ fs_eth_function[1] = (struct usb_descriptor_header *) &subset_data_intf; fs_eth_function[2] = (struct usb_descriptor_header *) &header_desc; @@ -733,7 +718,7 @@ static const struct usb_descriptor_header *fs_rndis_function[] = { * descriptors, unless they only run at full speed. */
-#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) static struct usb_endpoint_descriptor hs_status_desc = { .bLength = USB_DT_ENDPOINT_SIZE, @@ -743,7 +728,7 @@ hs_status_desc = { .wMaxPacketSize = __constant_cpu_to_le16(STATUS_BYTECOUNT), .bInterval = LOG2_STATUS_INTERVAL_MSEC + 4, }; -#endif /* DEV_CONFIG_CDC */ +#endif /* CONFIG_USB_ETH_CDC */
static struct usb_endpoint_descriptor hs_source_desc = { @@ -776,7 +761,7 @@ dev_qualifier = {
static const struct usb_descriptor_header *hs_eth_function[11] = { (struct usb_descriptor_header *) &otg_descriptor, -#ifdef DEV_CONFIG_CDC +#ifdef CONFIG_USB_ETH_CDC /* "cdc" mode descriptors */ (struct usb_descriptor_header *) &control_intf, (struct usb_descriptor_header *) &header_desc, @@ -790,12 +775,12 @@ static const struct usb_descriptor_header *hs_eth_function[11] = { (struct usb_descriptor_header *) &hs_source_desc, (struct usb_descriptor_header *) &hs_sink_desc, NULL, -#endif /* DEV_CONFIG_CDC */ +#endif /* CONFIG_USB_ETH_CDC */ };
static inline void hs_subset_descriptors(void) { -#ifdef DEV_CONFIG_SUBSET +#ifdef CONFIG_USB_ETH_SUBSET /* behavior is "CDC Subset"; extra descriptors say "SAFE" */ hs_eth_function[1] = (struct usb_descriptor_header *) &subset_data_intf; hs_eth_function[2] = (struct usb_descriptor_header *) &header_desc; @@ -857,11 +842,11 @@ static struct usb_string strings[] = { { STRING_SERIALNUMBER, serial_number, }, { STRING_DATA, "Ethernet Data", }, { STRING_ETHADDR, ethaddr, }, -#ifdef DEV_CONFIG_CDC +#ifdef CONFIG_USB_ETH_CDC { STRING_CDC, "CDC Ethernet", }, { STRING_CONTROL, "CDC Communications Control", }, #endif -#ifdef DEV_CONFIG_SUBSET +#ifdef CONFIG_USB_ETH_SUBSET { STRING_SUBSET, "CDC Ethernet Subset", }, #endif #ifdef CONFIG_USB_ETH_RNDIS @@ -878,7 +863,7 @@ static struct usb_gadget_strings stringtab = {
/*============================================================================*/ static u8 control_req[USB_BUFSIZ]; -#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) static u8 status_req[STATUS_BYTECOUNT] __attribute__ ((aligned(4))); #endif
@@ -967,7 +952,7 @@ set_ether_config(struct eth_dev *dev, gfp_t gfp_flags) int result = 0; struct usb_gadget *gadget = dev->gadget;
-#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) /* status endpoint used for RNDIS and (optionally) CDC */ if (!subset_active(dev) && dev->status_ep) { dev->status = ep_desc(gadget, &hs_status_desc, @@ -1148,7 +1133,7 @@ static int eth_set_config(struct eth_dev *dev, unsigned number,
/*-------------------------------------------------------------------------*/
-#ifdef DEV_CONFIG_CDC +#ifdef CONFIG_USB_ETH_CDC
/* * The interrupt endpoint is used in CDC networking models (Ethernet, ATM) @@ -1379,7 +1364,7 @@ eth_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) goto done_set_intf; }
-#ifdef DEV_CONFIG_CDC +#ifdef CONFIG_USB_ETH_CDC switch (wIndex) { case 0: /* control/master intf */ if (wValue != 0) @@ -1421,7 +1406,7 @@ eth_setup(struct usb_gadget *gadget, const struct usb_ctrlrequest *ctrl) * all non-PXA hardware talks real CDC ... */ debug("set_interface ignored!\n"); -#endif /* DEV_CONFIG_CDC */ +#endif /* CONFIG_USB_ETH_CDC */
done_set_intf: break; @@ -1444,7 +1429,7 @@ done_set_intf: value = min(wLength, (u16) 1); break;
-#ifdef DEV_CONFIG_CDC +#ifdef CONFIG_USB_ETH_CDC case USB_CDC_SET_ETHERNET_PACKET_FILTER: /* * see 6.2.30: no data, wIndex = interface, @@ -1468,7 +1453,7 @@ done_set_intf: * case USB_CDC_GET_ETHERNET_STATISTIC: */
-#endif /* DEV_CONFIG_CDC */ +#endif /* CONFIG_USB_ETH_CDC */
#ifdef CONFIG_USB_ETH_RNDIS /* @@ -2039,7 +2024,7 @@ static int eth_bind(struct usb_gadget *gadget) u8 tmp[7];
/* these flags are only ever cleared; compiler take note */ -#ifndef DEV_CONFIG_CDC +#ifndef CONFIG_USB_ETH_CDC cdc = 0; #endif #ifndef CONFIG_USB_ETH_RNDIS @@ -2151,7 +2136,7 @@ autoconf_fail: goto autoconf_fail; out_ep->driver_data = out_ep; /* claim */
-#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) /* * CDC Ethernet control interface doesn't require a status endpoint. * Since some hosts expect one, try to allocate one anyway. @@ -2163,7 +2148,7 @@ autoconf_fail: } else if (rndis) { error("can't run RNDIS on %s", gadget->name); return -ENODEV; -#ifdef DEV_CONFIG_CDC +#ifdef CONFIG_USB_ETH_CDC } else if (cdc) { control_intf.bNumEndpoints = 0; /* FIXME remove endpoint from descriptor list */ @@ -2206,7 +2191,7 @@ autoconf_fail: fs_source_desc.bEndpointAddress; hs_sink_desc.bEndpointAddress = fs_sink_desc.bEndpointAddress; -#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) if (status_ep) hs_status_desc.bEndpointAddress = fs_status_desc.bEndpointAddress; @@ -2275,7 +2260,7 @@ autoconf_fail: dev->req->complete = eth_setup_complete;
/* ... and maybe likewise for status transfer */ -#if defined(DEV_CONFIG_CDC) || defined(CONFIG_USB_ETH_RNDIS) +#if defined(CONFIG_USB_ETH_CDC) || defined(CONFIG_USB_ETH_RNDIS) if (dev->status_ep) { dev->stat_req = usb_ep_alloc_request(dev->status_ep, GFP_KERNEL);

Add to pxa25x based devices support for USB ethernet gadget. This is a port of pxa25x UDC driver from Linux kernel.
Signed-off-by: Lukasz Dalek luk0104@gmail.com --- drivers/usb/gadget/Makefile | 1 + drivers/usb/gadget/pxa25x_udc.c | 2059 +++++++++++++++++++++++++++++++++++++++ drivers/usb/gadget/pxa25x_udc.h | 162 +++ 3 files changed, 2222 insertions(+), 0 deletions(-) create mode 100644 drivers/usb/gadget/pxa25x_udc.c create mode 100644 drivers/usb/gadget/pxa25x_udc.h
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 5bbdd36..040eaba 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -36,6 +36,7 @@ ifdef CONFIG_USB_ETHER COBJS-y += ether.o epautoconf.o config.o usbstring.o COBJS-$(CONFIG_USB_ETH_RNDIS) += rndis.o COBJS-$(CONFIG_MV_UDC) += mv_udc.o +COBJS-$(CONFIG_CPU_PXA25X) += pxa25x_udc.o else # Devices not related to the new gadget layer depend on CONFIG_USB_DEVICE ifdef CONFIG_USB_DEVICE diff --git a/drivers/usb/gadget/pxa25x_udc.c b/drivers/usb/gadget/pxa25x_udc.c new file mode 100644 index 0000000..dd74143 --- /dev/null +++ b/drivers/usb/gadget/pxa25x_udc.c @@ -0,0 +1,2059 @@ +/* + * Intel PXA25x and IXP4xx on-chip full speed USB device controllers + * + * Copyright (C) 2002 Intrinsyc, Inc. (Frank Becker) + * Copyright (C) 2003 Robert Schwebel, Pengutronix + * Copyright (C) 2003 Benedikt Spranger, Pengutronix + * Copyright (C) 2003 David Brownell + * Copyright (C) 2003 Joshua Wise + * Copyright (C) 2012 Lukasz Dalek luk0104@gmail.com + * + * 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 + * + * MODULE_AUTHOR("Frank Becker, Robert Schwebel, David Brownell"); + */ + +#define CONFIG_USB_PXA25X_SMALL +#define DRIVER_NAME "pxa25x_udc_linux" +#define ARCH_HAS_PREFETCH + +#include <common.h> +#include <errno.h> +#include <asm/byteorder.h> +#include <asm/system.h> +#include <asm/mach-types.h> +#include <asm/unaligned.h> +#include <linux/compat.h> +#include <malloc.h> +#include <asm/io.h> +#include <asm/arch/pxa.h> + +#include <usbdescriptors.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <usb/lin_gadget_compat.h> +#include <asm/arch/pxa-regs.h> + +#include "pxa25x_udc.h" + +/* + * This driver handles the USB Device Controller (UDC) in Intel's PXA 25x + * series processors. The UDC for the IXP 4xx series is very similar. + * There are fifteen endpoints, in addition to ep0. + * + * Such controller drivers work with a gadget driver. The gadget driver + * returns descriptors, implements configuration and data protocols used + * by the host to interact with this device, and allocates endpoints to + * the different protocol interfaces. The controller driver virtualizes + * usb hardware so that the gadget drivers will be more portable. + * + * This UDC hardware wants to implement a bit too much USB protocol, so + * it constrains the sorts of USB configuration change events that work. + * The errata for these chips are misleading; some "fixed" bugs from + * pxa250 a0/a1 b0/b1/b2 sure act like they're still there. + * + * Note that the UDC hardware supports DMA (except on IXP) but that's + * not used here. IN-DMA (to host) is simple enough, when the data is + * suitably aligned (16 bytes) ... the network stack doesn't do that, + * other software can. OUT-DMA is buggy in most chip versions, as well + * as poorly designed (data toggle not automatic). So this driver won't + * bother using DMA. (Mostly-working IN-DMA support was available in + * kernels before 2.6.23, but was never enabled or well tested.) + */ + +#define DRIVER_VERSION "18-August-2012" +#define DRIVER_DESC "PXA 25x USB Device Controller driver" + +static const char driver_name[] = "pxa25x_udc"; +static const char ep0name[] = "ep0"; + +/* Watchdog */ +static inline void start_watchdog(struct pxa25x_udc *udc) +{ + debug("Started watchdog\n"); + udc->watchdog.base = get_timer(0); + udc->watchdog.running = 1; +} + +static inline void stop_watchdog(struct pxa25x_udc *udc) +{ + udc->watchdog.running = 0; + debug("Stopped watchdog\n"); +} + +static inline void test_watchdog(struct pxa25x_udc *udc) +{ + if (!udc->watchdog.running) + return; + + debug("watchdog %ld %ld\n", get_timer(udc->watchdog.base), + udc->watchdog.period); + + if (get_timer(udc->watchdog.base) >= udc->watchdog.period) { + stop_watchdog(udc); + udc->watchdog.function(udc); + } +} + +static void udc_watchdog(struct pxa25x_udc *dev) +{ + uint32_t udccs0 = readl(&dev->regs->udccs[0]); + + debug("Fired up udc_watchdog\n"); + + local_irq_disable(); + if (dev->ep0state == EP0_STALL + && (udccs0 & UDCCS0_FST) == 0 + && (udccs0 & UDCCS0_SST) == 0) { + writel(UDCCS0_FST|UDCCS0_FTF, &dev->regs->udccs[0]); + debug("ep0 re-stall\n"); + start_watchdog(dev); + } + local_irq_enable(); +} + +#ifdef DEBUG + +static const char * const state_name[] = { + "EP0_IDLE", + "EP0_IN_DATA_PHASE", "EP0_OUT_DATA_PHASE", + "EP0_END_XFER", "EP0_STALL" +}; + +static void +dump_udccr(const char *label) +{ + u32 udccr = readl(&UDC_REGS->udccr); + debug("%s %02X =%s%s%s%s%s%s%s%s\n", + label, udccr, + (udccr & UDCCR_REM) ? " rem" : "", + (udccr & UDCCR_RSTIR) ? " rstir" : "", + (udccr & UDCCR_SRM) ? " srm" : "", + (udccr & UDCCR_SUSIR) ? " susir" : "", + (udccr & UDCCR_RESIR) ? " resir" : "", + (udccr & UDCCR_RSM) ? " rsm" : "", + (udccr & UDCCR_UDA) ? " uda" : "", + (udccr & UDCCR_UDE) ? " ude" : ""); +} + +static void +dump_udccs0(const char *label) +{ + u32 udccs0 = readl(&UDC_REGS->udccs[0]); + + debug("%s %s %02X =%s%s%s%s%s%s%s%s\n", + label, state_name[the_controller->ep0state], udccs0, + (udccs0 & UDCCS0_SA) ? " sa" : "", + (udccs0 & UDCCS0_RNE) ? " rne" : "", + (udccs0 & UDCCS0_FST) ? " fst" : "", + (udccs0 & UDCCS0_SST) ? " sst" : "", + (udccs0 & UDCCS0_DRWF) ? " dwrf" : "", + (udccs0 & UDCCS0_FTF) ? " ftf" : "", + (udccs0 & UDCCS0_IPR) ? " ipr" : "", + (udccs0 & UDCCS0_OPR) ? " opr" : ""); +} + +static void +dump_state(struct pxa25x_udc *dev) +{ + u32 tmp; + unsigned i; + + debug("%s, uicr %02X.%02X, usir %02X.%02x, ufnr %02X.%02X\n", + state_name[dev->ep0state], + readl(&UDC_REGS->uicr1), readl(&UDC_REGS->uicr0), + readl(&UDC_REGS->usir1), readl(&UDC_REGS->usir0), + readl(&UDC_REGS->ufnrh), readl(&UDC_REGS->ufnrl)); + dump_udccr("udccr"); + if (dev->has_cfr) { + tmp = readl(&UDC_REGS->udccfr); + debug("udccfr %02X =%s%s\n", tmp, + (tmp & UDCCFR_AREN) ? " aren" : "", + (tmp & UDCCFR_ACM) ? " acm" : ""); + } + + if (!dev->driver) { + debug("no gadget driver bound\n"); + return; + } else + debug("ep0 driver '%s'\n", "ether"); + + dump_udccs0("udccs0"); + debug("ep0 IN %lu/%lu, OUT %lu/%lu\n", + dev->stats.write.bytes, dev->stats.write.ops, + dev->stats.read.bytes, dev->stats.read.ops); + + for (i = 1; i < PXA_UDC_NUM_ENDPOINTS; i++) { + if (dev->ep[i].desc == NULL) + continue; + debug("udccs%d = %02x\n", i, *dev->ep->reg_udccs); + } +} + +#else /* DEBUG */ + +static inline void dump_udccr(const char *label) { } +static inline void dump_udccs0(const char *label) { } +static inline void dump_state(struct pxa25x_udc *dev) { } + +#endif /* DEBUG */ + +/* + * --------------------------------------------------------------------------- + * endpoint related parts of the api to the usb controller hardware, + * used by gadget driver; and the inner talker-to-hardware core. + * --------------------------------------------------------------------------- + */ + +static void pxa25x_ep_fifo_flush(struct usb_ep *ep); +static void nuke(struct pxa25x_ep *, int status); + +/* one GPIO should control a D+ pullup, so host sees this device (or not) */ +static void pullup_off(void) +{ + struct pxa2xx_udc_mach_info *mach = the_controller->mach; + + if (mach->udc_command) + mach->udc_command(PXA2XX_UDC_CMD_DISCONNECT); +} + +static void pullup_on(void) +{ + struct pxa2xx_udc_mach_info *mach = the_controller->mach; + + if (mach->udc_command) + mach->udc_command(PXA2XX_UDC_CMD_CONNECT); +} + +static void pio_irq_enable(int bEndpointAddress) +{ + bEndpointAddress &= 0xf; + if (bEndpointAddress < 8) { + clrbits_le32(&the_controller->regs->uicr0, + 1 << bEndpointAddress); + } else { + bEndpointAddress -= 8; + clrbits_le32(&the_controller->regs->uicr1, + 1 << bEndpointAddress); + } +} + +static void pio_irq_disable(int bEndpointAddress) +{ + bEndpointAddress &= 0xf; + if (bEndpointAddress < 8) { + setbits_le32(&the_controller->regs->uicr0, + 1 << bEndpointAddress); + } else { + bEndpointAddress -= 8; + setbits_le32(&the_controller->regs->uicr1, + 1 << bEndpointAddress); + } +} + +static inline void udc_set_mask_UDCCR(int mask) +{ + /* + * The UDCCR reg contains mask and interrupt status bits, + * so using '|=' isn't safe as it may ack an interrupt. + */ + const uint32_t mask_bits = UDCCR_REM | UDCCR_SRM | UDCCR_UDE; + + mask &= mask_bits; + clrsetbits_le32(&the_controller->regs->udccr, ~mask_bits, mask); +} + +static inline void udc_clear_mask_UDCCR(int mask) +{ + const uint32_t mask_bits = UDCCR_REM | UDCCR_SRM | UDCCR_UDE; + + mask = ~mask & mask_bits; + clrbits_le32(&the_controller->regs->udccr, ~mask); +} + +static inline void udc_ack_int_UDCCR(int mask) +{ + const uint32_t mask_bits = UDCCR_REM | UDCCR_SRM | UDCCR_UDE; + + mask &= ~mask_bits; + clrsetbits_le32(&the_controller->regs->udccr, ~mask_bits, mask); +} + +/* + * endpoint enable/disable + * + * we need to verify the descriptors used to enable endpoints. since pxa25x + * endpoint configurations are fixed, and are pretty much always enabled, + * there's not a lot to manage here. + * + * because pxa25x can't selectively initialize bulk (or interrupt) endpoints, + * (resetting endpoint halt and toggle), SET_INTERFACE is unusable except + * for a single interface (with only the default altsetting) and for gadget + * drivers that don't halt endpoints (not reset by set_interface). that also + * means that if you use ISO, you must violate the USB spec rule that all + * iso endpoints must be in non-default altsettings. + */ +static int pxa25x_ep_enable(struct usb_ep *_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct pxa25x_ep *ep; + struct pxa25x_udc *dev; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (!_ep || !desc || ep->desc || _ep->name == ep0name + || desc->bDescriptorType != USB_DT_ENDPOINT + || ep->bEndpointAddress != desc->bEndpointAddress + || ep->fifo_size < le16_to_cpu(desc->wMaxPacketSize)) { + printf("%s, bad ep or descriptor\n", __func__); + 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) { + printf("%s, %s type mismatch\n", __func__, _ep->name); + return -EINVAL; + } + + /* hardware _could_ do smaller, but driver doesn't */ + if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK + && le16_to_cpu(desc->wMaxPacketSize) + != BULK_FIFO_SIZE) + || !desc->wMaxPacketSize) { + printf("%s, bad %s maxpacket\n", __func__, _ep->name); + return -ERANGE; + } + + dev = ep->dev; + if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { + printf("%s, bogus device state\n", __func__); + return -ESHUTDOWN; + } + + ep->desc = desc; + ep->stopped = 0; + ep->pio_irqs = 0; + ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize); + + /* flush fifo (mostly for OUT buffers) */ + pxa25x_ep_fifo_flush(_ep); + + /* ... reset halt state too, if we could ... */ + + debug("enabled %s\n", _ep->name); + return 0; +} + +static int pxa25x_ep_disable(struct usb_ep *_ep) +{ + struct pxa25x_ep *ep; + unsigned long flags; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (!_ep || !ep->desc) { + printf("%s, %s not enabled\n", __func__, + _ep ? ep->ep.name : NULL); + return -EINVAL; + } + local_irq_save(flags); + + nuke(ep, -ESHUTDOWN); + + /* flush fifo (mostly for IN buffers) */ + pxa25x_ep_fifo_flush(_ep); + + ep->desc = NULL; + ep->stopped = 1; + + local_irq_restore(flags); + debug("%s disabled\n", _ep->name); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* + * for the pxa25x, these can just wrap kmalloc/kfree. gadget drivers + * must still pass correctly initialized endpoints, since other controller + * drivers may care about how it's currently set up (dma issues etc). + */ + +/* + * pxa25x_ep_alloc_request - allocate a request data structure + */ +static struct usb_request * +pxa25x_ep_alloc_request(struct usb_ep *_ep, gfp_t gfp_flags) +{ + struct pxa25x_request *req; + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + INIT_LIST_HEAD(&req->queue); + return &req->req; +} + + +/* + * pxa25x_ep_free_request - deallocate a request data structure + */ +static void +pxa25x_ep_free_request(struct usb_ep *_ep, struct usb_request *_req) +{ + struct pxa25x_request *req; + + req = container_of(_req, struct pxa25x_request, req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +/*-------------------------------------------------------------------------*/ + +/* + * done - retire a request; caller blocked irqs + */ +static void done(struct pxa25x_ep *ep, struct pxa25x_request *req, int status) +{ + unsigned stopped = ep->stopped; + + list_del_init(&req->queue); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) + debug("complete %s req %p stat %d len %u/%u\n", + ep->ep.name, &req->req, 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; +} + + +static inline void ep0_idle(struct pxa25x_udc *dev) +{ + dev->ep0state = EP0_IDLE; +} + +static int +write_packet(u32 *uddr, struct pxa25x_request *req, unsigned max) +{ + u8 *buf; + unsigned length, count; + + debug("%s(): uddr %p\n", __func__, uddr); + + buf = req->req.buf + req->req.actual; + prefetch(buf); + + /* how big will this packet be? */ + length = min(req->req.length - req->req.actual, max); + req->req.actual += length; + + count = length; + while (likely(count--)) + writeb(*buf++, uddr); + + return length; +} + +/* + * write to an IN endpoint fifo, as many packets as possible. + * irqs will use this to write the rest later. + * caller guarantees at least one packet buffer is ready (or a zlp). + */ +static int +write_fifo(struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + unsigned max; + + max = le16_to_cpu(ep->desc->wMaxPacketSize); + do { + unsigned count; + int is_last, is_short; + + count = write_packet(ep->reg_uddr, req, max); + + /* last packet is usually short (or a zlp) */ + if (unlikely(count != max)) + is_last = is_short = 1; + else { + if (likely(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 = unlikely(max < ep->fifo_size); + } + + debug_cond(NOISY, "wrote %s %d bytes%s%s %d left %p\n", + ep->ep.name, count, + is_last ? "/L" : "", is_short ? "/S" : "", + req->req.length - req->req.actual, req); + + /* + * let loose that packet. maybe try writing another one, + * double buffering might work. TSP, TPC, and TFS + * bit values are the same for all normal IN endpoints. + */ + writel(UDCCS_BI_TPC, ep->reg_udccs); + if (is_short) + writel(UDCCS_BI_TSP, ep->reg_udccs); + + /* requests complete when all IN data is in the FIFO */ + if (is_last) { + done(ep, req, 0); + if (list_empty(&ep->queue)) + pio_irq_disable(ep->bEndpointAddress); + return 1; + } + + /* + * TODO experiment: how robust can fifo mode tweaking be? + * double buffering is off in the default fifo mode, which + * prevents TFS from being set here. + */ + + } while (readl(ep->reg_udccs) & UDCCS_BI_TFS); + return 0; +} + +/* + * caller asserts req->pending (ep0 irq status nyet cleared); starts + * ep0 data stage. these chips want very simple state transitions. + */ +static inline +void ep0start(struct pxa25x_udc *dev, u32 flags, const char *tag) +{ + writel(flags|UDCCS0_SA|UDCCS0_OPR, &dev->regs->udccs[0]); + writel(USIR0_IR0, &dev->regs->usir0); + dev->req_pending = 0; + debug_cond(NOISY, "%s() %s, udccs0: %02x/%02x usir: %X.%X\n", + __func__, tag, readl(&dev->regs->udccs[0]), flags, + readl(&dev->regs->usir1), readl(&dev->regs->usir0)); +} + +static int +write_ep0_fifo(struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + unsigned count; + int is_short; + + count = write_packet(&ep->dev->regs->uddr0, req, EP0_FIFO_SIZE); + ep->dev->stats.write.bytes += count; + + /* last packet "must be" short (or a zlp) */ + is_short = (count != EP0_FIFO_SIZE); + + debug_cond(NOISY, "ep0in %d bytes %d left %p\n", count, + req->req.length - req->req.actual, req); + + if (unlikely(is_short)) { + if (ep->dev->req_pending) + ep0start(ep->dev, UDCCS0_IPR, "short IN"); + else + writel(UDCCS0_IPR, &ep->dev->regs->udccs[0]); + + count = req->req.length; + done(ep, req, 0); + ep0_idle(ep->dev); + + /* + * This seems to get rid of lost status irqs in some cases: + * host responds quickly, or next request involves config + * change automagic, or should have been hidden, or ... + * + * FIXME get rid of all udelays possible... + */ + if (count >= EP0_FIFO_SIZE) { + count = 100; + do { + if ((readl(&ep->dev->regs->udccs[0]) & + UDCCS0_OPR) != 0) { + /* clear OPR, generate ack */ + writel(UDCCS0_OPR, + &ep->dev->regs->udccs[0]); + break; + } + count--; + udelay(1); + } while (count); + } + } else if (ep->dev->req_pending) + ep0start(ep->dev, 0, "IN"); + + return is_short; +} + + +/* + * read_fifo - unload packet(s) from the fifo we use for usb OUT + * transfers and put them into the request. caller should have made + * sure there's at least one packet ready. + * + * returns true if the request completed because of short packet or the + * request buffer having filled (and maybe overran till end-of-packet). + */ +static int +read_fifo(struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + u32 udccs; + u8 *buf; + unsigned bufferspace, count, is_short; + + for (;;) { + /* + * make sure there's a packet in the FIFO. + * UDCCS_{BO,IO}_RPC are all the same bit value. + * UDCCS_{BO,IO}_RNE are all the same bit value. + */ + udccs = readl(ep->reg_udccs); + if (unlikely((udccs & UDCCS_BO_RPC) == 0)) + break; + buf = req->req.buf + req->req.actual; + prefetchw(buf); + bufferspace = req->req.length - req->req.actual; + + /* read all bytes from this packet */ + if (likely(udccs & UDCCS_BO_RNE)) { + count = 1 + (0x0ff & readl(ep->reg_ubcr)); + req->req.actual += min(count, bufferspace); + } else /* zlp */ + count = 0; + is_short = (count < ep->ep.maxpacket); + debug_cond(NOISY, "read %s %02x, %d bytes%s req %p %d/%d\n", + ep->ep.name, udccs, count, + is_short ? "/S" : "", + req, req->req.actual, req->req.length); + while (likely(count-- != 0)) { + u8 byte = readb(ep->reg_uddr); + + 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) + printf("%s overflow %d\n", + ep->ep.name, count); + req->req.status = -EOVERFLOW; + } else { + *buf++ = byte; + bufferspace--; + } + } + writel(UDCCS_BO_RPC, ep->reg_udccs); + /* RPC/RSP/RNE could now reflect the other packet buffer */ + + /* iso is one request per packet */ + if (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + if (udccs & UDCCS_IO_ROF) + req->req.status = -EHOSTUNREACH; + /* more like "is_done" */ + is_short = 1; + } + + /* completion */ + if (is_short || req->req.actual == req->req.length) { + done(ep, req, 0); + if (list_empty(&ep->queue)) + pio_irq_disable(ep->bEndpointAddress); + return 1; + } + + /* finished that packet. the next one may be waiting... */ + } + return 0; +} + +/* + * special ep0 version of the above. no UBCR0 or double buffering; status + * handshaking is magic. most device protocols don't need control-OUT. + * CDC vendor commands (and RNDIS), mass storage CB/CBI, and some other + * protocols do use them. + */ +static int +read_ep0_fifo(struct pxa25x_ep *ep, struct pxa25x_request *req) +{ + u8 *buf, byte; + unsigned bufferspace; + + buf = req->req.buf + req->req.actual; + bufferspace = req->req.length - req->req.actual; + + while (readl(&ep->dev->regs->udccs[0]) & UDCCS0_RNE) { + byte = (u8)readb(&ep->dev->regs->uddr0); + + 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) + printf("%s overflow\n", ep->ep.name); + req->req.status = -EOVERFLOW; + } else { + *buf++ = byte; + req->req.actual++; + bufferspace--; + } + } + + writel(UDCCS0_OPR | UDCCS0_IPR, &ep->dev->regs->udccs[0]); + + /* completion */ + if (req->req.actual >= req->req.length) + return 1; + + /* finished that packet. the next one may be waiting... */ + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int +pxa25x_ep_queue(struct usb_ep *_ep, struct usb_request *_req, gfp_t gfp_flags) +{ + struct pxa25x_request *req; + struct pxa25x_ep *ep; + struct pxa25x_udc *dev; + unsigned long flags; + + req = container_of(_req, struct pxa25x_request, req); + if (unlikely(!_req || !_req->complete || !_req->buf + || !list_empty(&req->queue))) { + printf("%s, bad params\n", __func__); + return -EINVAL; + } + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { + printf("%s, bad ep\n", __func__); + return -EINVAL; + } + + dev = ep->dev; + if (unlikely(!dev->driver + || dev->gadget.speed == USB_SPEED_UNKNOWN)) { + printf("%s, bogus device state\n", __func__); + return -ESHUTDOWN; + } + + /* + * iso is always one packet per request, that's the only way + * we can report per-packet status. that also helps with dma. + */ + if (unlikely(ep->bmAttributes == USB_ENDPOINT_XFER_ISOC + && req->req.length > + le16_to_cpu(ep->desc->wMaxPacketSize))) + return -EMSGSIZE; + + debug_cond(NOISY, "%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; + + /* kickstart this i/o queue? */ + if (list_empty(&ep->queue) && !ep->stopped) { + if (ep->desc == NULL/* ep0 */) { + unsigned length = _req->length; + + switch (dev->ep0state) { + case EP0_IN_DATA_PHASE: + dev->stats.write.ops++; + if (write_ep0_fifo(ep, req)) + req = NULL; + break; + + case EP0_OUT_DATA_PHASE: + dev->stats.read.ops++; + /* messy ... */ + if (dev->req_config) { + debug("ep0 config ack%s\n", + dev->has_cfr ? "" : " raced"); + if (dev->has_cfr) + writel(UDCCFR_AREN|UDCCFR_ACM + |UDCCFR_MB1, + &ep->dev->regs->udccfr); + done(ep, req, 0); + dev->ep0state = EP0_END_XFER; + local_irq_restore(flags); + return 0; + } + if (dev->req_pending) + ep0start(dev, UDCCS0_IPR, "OUT"); + if (length == 0 || + ((readl( + &ep->dev->regs->udccs[0]) + & UDCCS0_RNE) != 0 + && read_ep0_fifo(ep, req))) { + ep0_idle(dev); + done(ep, req, 0); + req = NULL; + } + break; + + default: + printf("ep0 i/o, odd state %d\n", + dev->ep0state); + local_irq_restore(flags); + return -EL2HLT; + } + /* can the FIFO can satisfy the request immediately? */ + } else if ((ep->bEndpointAddress & USB_DIR_IN) != 0) { + if ((readl(ep->reg_udccs) & UDCCS_BI_TFS) != 0 + && write_fifo(ep, req)) + req = NULL; + } else if ((readl(ep->reg_udccs) & UDCCS_BO_RFS) != 0 + && read_fifo(ep, req)) { + req = NULL; + } + + if (likely(req && ep->desc)) + pio_irq_enable(ep->bEndpointAddress); + } + + /* pio or dma irq handler advances the queue. */ + if (likely(req != NULL)) + list_add_tail(&req->queue, &ep->queue); + local_irq_restore(flags); + + return 0; +} + + +/* + * nuke - dequeue ALL requests + */ +static void nuke(struct pxa25x_ep *ep, int status) +{ + struct pxa25x_request *req; + + /* called with irqs blocked */ + while (!list_empty(&ep->queue)) { + req = list_entry(ep->queue.next, + struct pxa25x_request, + queue); + done(ep, req, status); + } + if (ep->desc) + pio_irq_disable(ep->bEndpointAddress); +} + + +/* dequeue JUST ONE request */ +static int pxa25x_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req) +{ + struct pxa25x_ep *ep; + struct pxa25x_request *req; + unsigned long flags; + + ep = container_of(_ep, struct pxa25x_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; + } + + done(ep, req, -ECONNRESET); + + local_irq_restore(flags); + return 0; +} + +/*-------------------------------------------------------------------------*/ + +static int pxa25x_ep_set_halt(struct usb_ep *_ep, int value) +{ + struct pxa25x_ep *ep; + unsigned long flags; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (unlikely(!_ep + || (!ep->desc && ep->ep.name != ep0name)) + || ep->bmAttributes == USB_ENDPOINT_XFER_ISOC) { + printf("%s, bad ep\n", __func__); + return -EINVAL; + } + if (value == 0) { + /* + * this path (reset toggle+halt) is needed to implement + * SET_INTERFACE on normal hardware. but it can't be + * done from software on the PXA UDC, and the hardware + * forgets to do it as part of SET_INTERFACE automagic. + */ + printf("only host can clear %s halt\n", _ep->name); + return -EROFS; + } + + local_irq_save(flags); + + if ((ep->bEndpointAddress & USB_DIR_IN) != 0 + && ((readl(ep->reg_udccs) & UDCCS_BI_TFS) == 0 + || !list_empty(&ep->queue))) { + local_irq_restore(flags); + return -EAGAIN; + } + + /* FST bit is the same for control, bulk in, bulk out, interrupt in */ + writel(UDCCS_BI_FST|UDCCS_BI_FTF, ep->reg_udccs); + + /* ep0 needs special care */ + if (!ep->desc) { + start_watchdog(ep->dev); + ep->dev->req_pending = 0; + ep->dev->ep0state = EP0_STALL; + + /* and bulk/intr endpoints like dropping stalls too */ + } else { + unsigned i; + for (i = 0; i < 1000; i += 20) { + if (readl(ep->reg_udccs) & UDCCS_BI_SST) + break; + udelay(20); + } + } + local_irq_restore(flags); + + debug("%s halt\n", _ep->name); + return 0; +} + +static int pxa25x_ep_fifo_status(struct usb_ep *_ep) +{ + struct pxa25x_ep *ep; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (!_ep) { + printf("%s, bad ep\n", __func__); + return -ENODEV; + } + /* pxa can't report unclaimed bytes from IN fifos */ + if ((ep->bEndpointAddress & USB_DIR_IN) != 0) + return -EOPNOTSUPP; + if (ep->dev->gadget.speed == USB_SPEED_UNKNOWN + || (readl(ep->reg_udccs) & UDCCS_BO_RFS) == 0) + return 0; + else + return (readl(ep->reg_ubcr) & 0xfff) + 1; +} + +static void pxa25x_ep_fifo_flush(struct usb_ep *_ep) +{ + struct pxa25x_ep *ep; + + ep = container_of(_ep, struct pxa25x_ep, ep); + if (!_ep || ep->ep.name == ep0name || !list_empty(&ep->queue)) { + printf("%s, bad ep\n", __func__); + return; + } + + /* toggle and halt bits stay unchanged */ + + /* for OUT, just read and discard the FIFO contents. */ + if ((ep->bEndpointAddress & USB_DIR_IN) == 0) { + while (((readl(ep->reg_udccs)) & UDCCS_BO_RNE) != 0) + (void)readb(ep->reg_uddr); + return; + } + + /* most IN status is the same, but ISO can't stall */ + writel(UDCCS_BI_TPC|UDCCS_BI_FTF|UDCCS_BI_TUR + | (ep->bmAttributes == USB_ENDPOINT_XFER_ISOC + ? 0 : UDCCS_BI_SST), ep->reg_udccs); +} + + +static struct usb_ep_ops pxa25x_ep_ops = { + .enable = pxa25x_ep_enable, + .disable = pxa25x_ep_disable, + + .alloc_request = pxa25x_ep_alloc_request, + .free_request = pxa25x_ep_free_request, + + .queue = pxa25x_ep_queue, + .dequeue = pxa25x_ep_dequeue, + + .set_halt = pxa25x_ep_set_halt, + .fifo_status = pxa25x_ep_fifo_status, + .fifo_flush = pxa25x_ep_fifo_flush, +}; + + +/* --------------------------------------------------------------------------- + * device-scoped parts of the api to the usb controller hardware + * --------------------------------------------------------------------------- + */ + +static int pxa25x_udc_get_frame(struct usb_gadget *_gadget) +{ + return ((readl(&the_controller->regs->ufnrh) & 0x07) << 8) | + (readl(&the_controller->regs->ufnrl) & 0xff); +} + +static int pxa25x_udc_wakeup(struct usb_gadget *_gadget) +{ + /* host may not have enabled remote wakeup */ + if ((readl(&the_controller->regs->udccs[0]) & UDCCS0_DRWF) == 0) + return -EHOSTUNREACH; + udc_set_mask_UDCCR(UDCCR_RSM); + return 0; +} + +static void stop_activity(struct pxa25x_udc *, struct usb_gadget_driver *); +static void udc_enable(struct pxa25x_udc *); +static void udc_disable(struct pxa25x_udc *); + +/* + * We disable the UDC -- and its 48 MHz clock -- whenever it's not + * in active use. + */ +static int pullup(struct pxa25x_udc *udc) +{ + if (udc->pullup) + pullup_on(); + else + pullup_off(); + + + int is_active = udc->pullup; + if (is_active) { + if (!udc->active) { + udc->active = 1; + udc_enable(udc); + } + } else { + if (udc->active) { + if (udc->gadget.speed != USB_SPEED_UNKNOWN) + stop_activity(udc, udc->driver); + udc_disable(udc); + udc->active = 0; + } + + } + return 0; +} + +/* VBUS reporting logically comes from a transceiver */ +static int pxa25x_udc_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct pxa25x_udc *udc; + + udc = container_of(_gadget, struct pxa25x_udc, gadget); + printf("vbus %s\n", is_active ? "supplied" : "inactive"); + pullup(udc); + return 0; +} + +/* drivers may have software control over D+ pullup */ +static int pxa25x_udc_pullup(struct usb_gadget *_gadget, int is_active) +{ + struct pxa25x_udc *udc; + + udc = container_of(_gadget, struct pxa25x_udc, gadget); + + /* not all boards support pullup control */ + if (!udc->mach->udc_command) + return -EOPNOTSUPP; + + udc->pullup = (is_active != 0); + pullup(udc); + return 0; +} + +/* + * boards may consume current from VBUS, up to 100-500mA based on config. + * the 500uA suspend ceiling means that exclusively vbus-powered PXA designs + * violate USB specs. + */ +static int pxa25x_udc_vbus_draw(struct usb_gadget *_gadget, unsigned mA) +{ + return -EOPNOTSUPP; +} + +static const struct usb_gadget_ops pxa25x_udc_ops = { + .get_frame = pxa25x_udc_get_frame, + .wakeup = pxa25x_udc_wakeup, + .vbus_session = pxa25x_udc_vbus_session, + .pullup = pxa25x_udc_pullup, + .vbus_draw = pxa25x_udc_vbus_draw, +}; + +/*-------------------------------------------------------------------------*/ + +/* + * udc_disable - disable USB device controller + */ +static void udc_disable(struct pxa25x_udc *dev) +{ + /* block all irqs */ + udc_set_mask_UDCCR(UDCCR_SRM|UDCCR_REM); + writel(0xff, &dev->regs->uicr0); + writel(0xff, &dev->regs->uicr1); + writel(UFNRH_SIM, &dev->regs->ufnrh); + + /* if hardware supports it, disconnect from usb */ + pullup_off(); + + udc_clear_mask_UDCCR(UDCCR_UDE); + + ep0_idle(dev); + dev->gadget.speed = USB_SPEED_UNKNOWN; +} + +/* + * udc_reinit - initialize software state + */ +static void udc_reinit(struct pxa25x_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 = EP0_IDLE; + + /* basic endpoint records init */ + for (i = 0; i < PXA_UDC_NUM_ENDPOINTS; i++) { + struct pxa25x_ep *ep = &dev->ep[i]; + + if (i != 0) + list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); + + ep->desc = NULL; + ep->stopped = 0; + INIT_LIST_HEAD(&ep->queue); + ep->pio_irqs = 0; + } + + /* the rest was statically initialized, and is read-only */ +} + +/* + * until it's enabled, this UDC should be completely invisible + * to any USB host. + */ +static void udc_enable(struct pxa25x_udc *dev) +{ + debug("udc: enabling udc\n"); + + udc_clear_mask_UDCCR(UDCCR_UDE); + + /* + * Try to clear these bits before we enable the udc. + * Do not touch reset ack bit, we would take care of it in + * interrupt handle routine + */ + udc_ack_int_UDCCR(UDCCR_SUSIR|UDCCR_RESIR); + + ep0_idle(dev); + dev->gadget.speed = USB_SPEED_UNKNOWN; + dev->stats.irqs = 0; + + /* + * sequence taken from chapter 12.5.10, PXA250 AppProcDevManual: + * - enable UDC + * - if RESET is already in progress, ack interrupt + * - unmask reset interrupt + */ + udc_set_mask_UDCCR(UDCCR_UDE); + if (!(readl(&dev->regs->udccr) & UDCCR_UDA)) + udc_ack_int_UDCCR(UDCCR_RSTIR); + + if (dev->has_cfr /* UDC_RES2 is defined */) { + /* + * pxa255 (a0+) can avoid a set_config race that could + * prevent gadget drivers from configuring correctly + */ + writel(UDCCFR_ACM | UDCCFR_MB1, &dev->regs->udccfr); + } + + /* enable suspend/resume and reset irqs */ + udc_clear_mask_UDCCR(UDCCR_SRM | UDCCR_REM); + + /* enable ep0 irqs */ + clrbits_le32(&dev->regs->uicr0, UICR0_IM0); + + /* if hardware supports it, pullup D+ and wait for reset */ + pullup_on(); +} + +static inline void clear_ep_state(struct pxa25x_udc *dev) +{ + unsigned i; + + /* + * hardware SET_{CONFIGURATION,INTERFACE} automagic resets endpoint + * fifos, and pending transactions mustn't be continued in any case. + */ + for (i = 1; i < PXA_UDC_NUM_ENDPOINTS; i++) + nuke(&dev->ep[i], -ECONNABORTED); +} + +static void handle_ep0(struct pxa25x_udc *dev) +{ + u32 udccs0 = readl(&dev->regs->udccs[0]); + struct pxa25x_ep *ep = &dev->ep[0]; + struct pxa25x_request *req; + union { + struct usb_ctrlrequest r; + u8 raw[8]; + u32 word[2]; + } u; + + if (list_empty(&ep->queue)) + req = NULL; + else + req = list_entry(ep->queue.next, struct pxa25x_request, queue); + + /* clear stall status */ + if (udccs0 & UDCCS0_SST) { + nuke(ep, -EPIPE); + writel(UDCCS0_SST, &dev->regs->udccs[0]); + stop_watchdog(dev); + ep0_idle(dev); + } + + /* previous request unfinished? non-error iff back-to-back ... */ + if ((udccs0 & UDCCS0_SA) != 0 && dev->ep0state != EP0_IDLE) { + nuke(ep, 0); + stop_watchdog(dev); + ep0_idle(dev); + } + + switch (dev->ep0state) { + case EP0_IDLE: + /* late-breaking status? */ + udccs0 = readl(&dev->regs->udccs[0]); + + /* start control request? */ + if (likely((udccs0 & (UDCCS0_OPR|UDCCS0_SA|UDCCS0_RNE)) + == (UDCCS0_OPR|UDCCS0_SA|UDCCS0_RNE))) { + int i; + + nuke(ep, -EPROTO); + + /* read SETUP packet */ + for (i = 0; i < 8; i++) { + if (unlikely(!(readl(&dev->regs->udccs[0]) & + UDCCS0_RNE))) { +bad_setup: + debug("SETUP %d!\n", i); + goto stall; + } + u.raw[i] = (u8)readb(&dev->regs->uddr0); + } + if (unlikely((readl(&dev->regs->udccs[0]) & + UDCCS0_RNE) != 0)) + goto bad_setup; + +got_setup: + debug("SETUP %02x.%02x v%04x i%04x l%04x\n", + u.r.bRequestType, u.r.bRequest, + le16_to_cpu(u.r.wValue), + le16_to_cpu(u.r.wIndex), + le16_to_cpu(u.r.wLength)); + + /* cope with automagic for some standard requests. */ + dev->req_std = (u.r.bRequestType & USB_TYPE_MASK) + == USB_TYPE_STANDARD; + dev->req_config = 0; + dev->req_pending = 1; + switch (u.r.bRequest) { + /* hardware restricts gadget drivers here! */ + case USB_REQ_SET_CONFIGURATION: + debug("GOT SET_CONFIGURATION\n"); + if (u.r.bRequestType == USB_RECIP_DEVICE) { + /* + * reflect hardware's automagic + * up to the gadget driver. + */ +config_change: + dev->req_config = 1; + clear_ep_state(dev); + /* + * if !has_cfr, there's no synch + * else use AREN (later) not SA|OPR + * USIR0_IR0 acts edge sensitive + */ + } + break; + /* ... and here, even more ... */ + case USB_REQ_SET_INTERFACE: + if (u.r.bRequestType == USB_RECIP_INTERFACE) { + /* + * udc hardware is broken by design: + * - altsetting may only be zero; + * - hw resets all interfaces' eps; + * - ep reset doesn't include halt(?). + */ + printf("broken set_interface (%d/%d)\n", + le16_to_cpu(u.r.wIndex), + le16_to_cpu(u.r.wValue)); + goto config_change; + } + break; + /* hardware was supposed to hide this */ + case USB_REQ_SET_ADDRESS: + debug("GOT SET ADDRESS\n"); + if (u.r.bRequestType == USB_RECIP_DEVICE) { + ep0start(dev, 0, "address"); + return; + } + break; + } + + if (u.r.bRequestType & USB_DIR_IN) + dev->ep0state = EP0_IN_DATA_PHASE; + else + dev->ep0state = EP0_OUT_DATA_PHASE; + + i = dev->driver->setup(&dev->gadget, &u.r); + if (i < 0) { + /* hardware automagic preventing STALL... */ + if (dev->req_config) { + /* + * hardware sometimes neglects to tell + * tell us about config change events, + * so later ones may fail... + */ + printf("config change %02x fail %d?\n", + u.r.bRequest, i); + return; + /* + * TODO experiment: if has_cfr, + * hardware didn't ACK; maybe we + * could actually STALL! + */ + } + if (0) { +stall: + /* uninitialized when goto stall */ + i = 0; + } + debug("protocol STALL, " + "%02x err %d\n", + readl(&dev->regs->udccs[0]), i); + + /* + * the watchdog timer helps deal with cases + * where udc seems to clear FST wrongly, and + * then NAKs instead of STALLing. + */ + ep0start(dev, UDCCS0_FST|UDCCS0_FTF, "stall"); + start_watchdog(dev); + dev->ep0state = EP0_STALL; + + /* deferred i/o == no response yet */ + } else if (dev->req_pending) { + if (likely(dev->ep0state == EP0_IN_DATA_PHASE + || dev->req_std || u.r.wLength)) + ep0start(dev, 0, "defer"); + else + ep0start(dev, UDCCS0_IPR, "defer/IPR"); + } + + /* expect at least one data or status stage irq */ + return; + + } else if (likely((udccs0 & (UDCCS0_OPR|UDCCS0_SA)) + == (UDCCS0_OPR|UDCCS0_SA))) { + unsigned i; + + /* + * pxa210/250 erratum 131 for B0/B1 says RNE lies. + * still observed on a pxa255 a0. + */ + debug("e131\n"); + nuke(ep, -EPROTO); + + /* read SETUP data, but don't trust it too much */ + for (i = 0; i < 8; i++) + u.raw[i] = (u8)readb(&dev->regs->uddr0); + if ((u.r.bRequestType & USB_RECIP_MASK) + > USB_RECIP_OTHER) + goto stall; + if (u.word[0] == 0 && u.word[1] == 0) + goto stall; + goto got_setup; + } else { + /* + * some random early IRQ: + * - we acked FST + * - IPR cleared + * - OPR got set, without SA (likely status stage) + */ + debug("random IRQ %X %X\n", udccs0, + readl(&dev->regs->udccs[0])); + writel(udccs0 & (UDCCS0_SA|UDCCS0_OPR), + &dev->regs->udccs[0]); + } + break; + case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR etc */ + if (udccs0 & UDCCS0_OPR) { + debug("ep0in premature status\n"); + if (req) + done(ep, req, 0); + ep0_idle(dev); + } else /* irq was IPR clearing */ { + if (req) { + debug("next ep0 in packet\n"); + /* this IN packet might finish the request */ + (void) write_ep0_fifo(ep, req); + } /* else IN token before response was written */ + } + break; + case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR etc */ + if (udccs0 & UDCCS0_OPR) { + if (req) { + /* this OUT packet might finish the request */ + if (read_ep0_fifo(ep, req)) + done(ep, req, 0); + /* else more OUT packets expected */ + } /* else OUT token before read was issued */ + } else /* irq was IPR clearing */ { + debug("ep0out premature status\n"); + if (req) + done(ep, req, 0); + ep0_idle(dev); + } + break; + case EP0_END_XFER: + if (req) + done(ep, req, 0); + /* + * ack control-IN status (maybe in-zlp was skipped) + * also appears after some config change events. + */ + if (udccs0 & UDCCS0_OPR) + writel(UDCCS0_OPR, &dev->regs->udccs[0]); + ep0_idle(dev); + break; + case EP0_STALL: + writel(UDCCS0_FST, &dev->regs->udccs[0]); + break; + } + + writel(USIR0_IR0, &dev->regs->usir0); +} + +static void handle_ep(struct pxa25x_ep *ep) +{ + struct pxa25x_request *req; + int is_in = ep->bEndpointAddress & USB_DIR_IN; + int completed; + u32 udccs, tmp; + + do { + completed = 0; + if (likely(!list_empty(&ep->queue))) + req = list_entry(ep->queue.next, + struct pxa25x_request, queue); + else + req = NULL; + + /* TODO check FST handling */ + + udccs = readl(ep->reg_udccs); + if (unlikely(is_in)) { /* irq from TPC, SST, or (ISO) TUR */ + tmp = UDCCS_BI_TUR; + if (likely(ep->bmAttributes == USB_ENDPOINT_XFER_BULK)) + tmp |= UDCCS_BI_SST; + tmp &= udccs; + if (likely(tmp)) + writel(tmp, ep->reg_udccs); + if (req && likely((udccs & UDCCS_BI_TFS) != 0)) + completed = write_fifo(ep, req); + + } else { /* irq from RPC (or for ISO, ROF) */ + if (likely(ep->bmAttributes == USB_ENDPOINT_XFER_BULK)) + tmp = UDCCS_BO_SST | UDCCS_BO_DME; + else + tmp = UDCCS_IO_ROF | UDCCS_IO_DME; + tmp &= udccs; + if (likely(tmp)) + writel(tmp, ep->reg_udccs); + + /* fifos can hold packets, ready for reading... */ + if (likely(req)) + completed = read_fifo(ep, req); + else + pio_irq_disable(ep->bEndpointAddress); + } + ep->pio_irqs++; + } while (completed); +} + +/* + * pxa25x_udc_irq - interrupt handler + * + * avoid delays in ep0 processing. the control handshaking isn't always + * under software control (pxa250c0 and the pxa255 are better), and delays + * could cause usb protocol errors. + */ +static struct pxa25x_udc memory; +static int +pxa25x_udc_irq(void) +{ + struct pxa25x_udc *dev = &memory; + int handled; + + test_watchdog(dev); + + dev->stats.irqs++; + do { + u32 udccr = readl(&dev->regs->udccr); + + handled = 0; + + /* SUSpend Interrupt Request */ + if (unlikely(udccr & UDCCR_SUSIR)) { + udc_ack_int_UDCCR(UDCCR_SUSIR); + handled = 1; + debug("USB suspend\n"); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->suspend) + dev->driver->suspend(&dev->gadget); + ep0_idle(dev); + } + + /* RESume Interrupt Request */ + if (unlikely(udccr & UDCCR_RESIR)) { + udc_ack_int_UDCCR(UDCCR_RESIR); + handled = 1; + debug("USB resume\n"); + + if (dev->gadget.speed != USB_SPEED_UNKNOWN + && dev->driver + && dev->driver->resume) + dev->driver->resume(&dev->gadget); + } + + /* ReSeT Interrupt Request - USB reset */ + if (unlikely(udccr & UDCCR_RSTIR)) { + udc_ack_int_UDCCR(UDCCR_RSTIR); + handled = 1; + + if ((readl(&dev->regs->udccr) & UDCCR_UDA) == 0) { + debug("USB reset start\n"); + + /* + * reset driver and endpoints, + * in case that's not yet done + */ + stop_activity(dev, dev->driver); + + } else { + debug("USB reset end\n"); + dev->gadget.speed = USB_SPEED_FULL; + memset(&dev->stats, 0, sizeof dev->stats); + /* driver and endpoints are still reset */ + } + + } else { + u32 uicr0 = readl(&dev->regs->uicr0); + u32 uicr1 = readl(&dev->regs->uicr1); + u32 usir0 = readl(&dev->regs->usir0); + u32 usir1 = readl(&dev->regs->usir1); + + usir0 = usir0 & ~uicr0; + usir1 = usir1 & ~uicr1; + int i; + + if (unlikely(!usir0 && !usir1)) + continue; + + debug_cond(NOISY, "irq %02x.%02x\n", usir1, usir0); + + /* control traffic */ + if (usir0 & USIR0_IR0) { + dev->ep[0].pio_irqs++; + handle_ep0(dev); + handled = 1; + } + + /* endpoint data transfers */ + for (i = 0; i < 8; i++) { + u32 tmp = 1 << i; + + if (i && (usir0 & tmp)) { + handle_ep(&dev->ep[i]); + setbits_le32(&dev->regs->usir0, tmp); + handled = 1; + } +#ifndef CONFIG_USB_PXA25X_SMALL + if (usir1 & tmp) { + handle_ep(&dev->ep[i+8]); + setbits_le32(&dev->regs->usir1, tmp); + handled = 1; + } +#endif + } + } + + /* we could also ask for 1 msec SOF (SIR) interrupts */ + + } while (handled); + return IRQ_HANDLED; +} + +/*-------------------------------------------------------------------------*/ + +/* + * this uses load-time allocation and initialization (instead of + * doing it at run-time) to save code, eliminate fault paths, and + * be more obviously correct. + */ +static struct pxa25x_udc memory = { + .regs = UDC_REGS, + + .gadget = { + .ops = &pxa25x_udc_ops, + .ep0 = &memory.ep[0].ep, + .name = driver_name, + }, + + /* control endpoint */ + .ep[0] = { + .ep = { + .name = ep0name, + .ops = &pxa25x_ep_ops, + .maxpacket = EP0_FIFO_SIZE, + }, + .dev = &memory, + .reg_udccs = &UDC_REGS->udccs[0], + .reg_uddr = &UDC_REGS->uddr0, + }, + + /* first group of endpoints */ + .ep[1] = { + .ep = { + .name = "ep1in-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDC_REGS->udccs[1], + .reg_uddr = &UDC_REGS->uddr1, + }, + .ep[2] = { + .ep = { + .name = "ep2out-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = 2, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDC_REGS->udccs[2], + .reg_ubcr = &UDC_REGS->ubcr2, + .reg_uddr = &UDC_REGS->uddr2, + }, +#ifndef CONFIG_USB_PXA25X_SMALL + .ep[3] = { + .ep = { + .name = "ep3in-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 3, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDC_REGS->udccs[3], + .reg_uddr = &UDC_REGS->uddr3, + }, + .ep[4] = { + .ep = { + .name = "ep4out-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = 4, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDC_REGS->udccs[4], + .reg_ubcr = &UDC_REGS->ubcr4, + .reg_uddr = &UDC_REGS->uddr4, + }, + .ep[5] = { + .ep = { + .name = "ep5in-int", + .ops = &pxa25x_ep_ops, + .maxpacket = INT_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = INT_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 5, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .reg_udccs = &UDC_REGS->udccs[5], + .reg_uddr = &UDC_REGS->uddr5, + }, + + /* second group of endpoints */ + .ep[6] = { + .ep = { + .name = "ep6in-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 6, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDC_REGS->udccs[6], + .reg_uddr = &UDC_REGS->uddr6, + }, + .ep[7] = { + .ep = { + .name = "ep7out-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = 7, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDC_REGS->udccs[7], + .reg_ubcr = &UDC_REGS->ubcr7, + .reg_uddr = &UDC_REGS->uddr7, + }, + .ep[8] = { + .ep = { + .name = "ep8in-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 8, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDC_REGS->udccs[8], + .reg_uddr = &UDC_REGS->uddr8, + }, + .ep[9] = { + .ep = { + .name = "ep9out-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = 9, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDC_REGS->udccs[9], + .reg_ubcr = &UDC_REGS->ubcr9, + .reg_uddr = &UDC_REGS->uddr9, + }, + .ep[10] = { + .ep = { + .name = "ep10in-int", + .ops = &pxa25x_ep_ops, + .maxpacket = INT_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = INT_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 10, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .reg_udccs = &UDC_REGS->udccs[10], + .reg_uddr = &UDC_REGS->uddr10, + }, + + /* third group of endpoints */ + .ep[11] = { + .ep = { + .name = "ep11in-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 11, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDC_REGS->udccs[11], + .reg_uddr = &UDC_REGS->uddr11, + }, + .ep[12] = { + .ep = { + .name = "ep12out-bulk", + .ops = &pxa25x_ep_ops, + .maxpacket = BULK_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = BULK_FIFO_SIZE, + .bEndpointAddress = 12, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .reg_udccs = &UDC_REGS->udccs[12], + .reg_ubcr = &UDC_REGS->ubcr12, + .reg_uddr = &UDC_REGS->uddr12, + }, + .ep[13] = { + .ep = { + .name = "ep13in-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 13, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDC_REGS->udccs[13], + .reg_uddr = &UDC_REGS->uddr13, + }, + .ep[14] = { + .ep = { + .name = "ep14out-iso", + .ops = &pxa25x_ep_ops, + .maxpacket = ISO_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = ISO_FIFO_SIZE, + .bEndpointAddress = 14, + .bmAttributes = USB_ENDPOINT_XFER_ISOC, + .reg_udccs = &UDC_REGS->udccs[14], + .reg_ubcr = &UDC_REGS->ubcr14, + .reg_uddr = &UDC_REGS->uddr14, + }, + .ep[15] = { + .ep = { + .name = "ep15in-int", + .ops = &pxa25x_ep_ops, + .maxpacket = INT_FIFO_SIZE, + }, + .dev = &memory, + .fifo_size = INT_FIFO_SIZE, + .bEndpointAddress = USB_DIR_IN | 15, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .reg_udccs = &UDC_REGS->udccs[15], + .reg_uddr = &UDC_REGS->uddr15, + }, +#endif /* !CONFIG_USB_PXA25X_SMALL */ +}; + +static void udc_command(int cmd) +{ + switch (cmd) { + case PXA2XX_UDC_CMD_CONNECT: + setbits_le32(GPDR(CONFIG_USB_DEV_PULLUP_GPIO), + GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO)); + + /* enable pullup */ + writel(GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO), + GPCR(CONFIG_USB_DEV_PULLUP_GPIO)); + + debug("Connected to USB\n"); + break; + + case PXA2XX_UDC_CMD_DISCONNECT: + /* disable pullup resistor */ + writel(GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO), + GPSR(CONFIG_USB_DEV_PULLUP_GPIO)); + + /* setup pin as input, line will float */ + clrbits_le32(GPDR(CONFIG_USB_DEV_PULLUP_GPIO), + GPIO_bit(CONFIG_USB_DEV_PULLUP_GPIO)); + + debug("Disconnected from USB\n"); + break; + } +} + +static struct pxa2xx_udc_mach_info mach_info = { + .udc_command = udc_command, +}; + +/* + * when a driver is successfully registered, it will receive + * control requests including set_configuration(), which enables + * non-control requests. then usb traffic follows until a + * disconnect is reported. then a host may connect again, or + * the driver might get unbound. + */ +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct pxa25x_udc *dev = &memory; + int retval; + uint32_t chiprev; + + if (!driver + || driver->speed < USB_SPEED_FULL + || !driver->disconnect + || !driver->setup) + return -EINVAL; + if (!dev) + return -ENODEV; + if (dev->driver) + return -EBUSY; + + /* Enable clock for usb controller */ + setbits_le32(CKEN, CKEN11_USB); + + /* first hook up the driver ... */ + dev->driver = driver; + dev->pullup = 1; + + /* trigger chiprev-specific logic */ + switch ((chiprev = pxa_get_cpu_revision())) { + case PXA255_A0: + dev->has_cfr = 1; + break; + case PXA250_A0: + case PXA250_A1: + /* A0/A1 "not released"; ep 13, 15 unusable */ + /* fall through */ + case PXA250_B2: case PXA210_B2: + case PXA250_B1: case PXA210_B1: + case PXA250_B0: case PXA210_B0: + /* OUT-DMA is broken ... */ + /* fall through */ + case PXA250_C0: case PXA210_C0: + break; + default: + printf("%s: unrecognized processor: %08x\n", + DRIVER_NAME, chiprev); + return -ENODEV; + } + + the_controller = dev; + + /* prepare watchdog timer */ + dev->watchdog.running = 0; + dev->watchdog.period = 5000 * CONFIG_SYS_HZ / 1000000; /* 5 ms */ + dev->watchdog.function = udc_watchdog; + + udc_disable(dev); + udc_reinit(dev); + + dev->mach = &mach_info; + + dev->gadget.name = "pxa2xx_udc"; + retval = driver->bind(&dev->gadget); + if (retval) { + printf("bind to driver %s --> error %d\n", + DRIVER_NAME, retval); + dev->driver = NULL; + return retval; + } + + /* + * ... then enable host detection and ep0; and we're ready + * for set_configuration as well as eventual disconnect. + */ + printf("registered gadget driver '%s'\n", DRIVER_NAME); + + pullup(dev); + dump_state(dev); + return 0; +} + +static void +stop_activity(struct pxa25x_udc *dev, struct usb_gadget_driver *driver) +{ + int i; + + /* don't disconnect drivers more than once */ + if (dev->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + dev->gadget.speed = USB_SPEED_UNKNOWN; + + /* prevent new request submissions, kill any outstanding requests */ + for (i = 0; i < PXA_UDC_NUM_ENDPOINTS; i++) { + struct pxa25x_ep *ep = &dev->ep[i]; + + ep->stopped = 1; + nuke(ep, -ESHUTDOWN); + } + stop_watchdog(dev); + + /* report disconnect; the driver is already quiesced */ + if (driver) + driver->disconnect(&dev->gadget); + + /* re-init driver-visible data structures */ + udc_reinit(dev); +} + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct pxa25x_udc *dev = the_controller; + + if (!dev) + return -ENODEV; + if (!driver || driver != dev->driver || !driver->unbind) + return -EINVAL; + + local_irq_disable(); + dev->pullup = 0; + pullup(dev); + stop_activity(dev, driver); + local_irq_enable(); + + driver->unbind(&dev->gadget); + dev->driver = NULL; + + printf("unregistered gadget driver '%s'\n", DRIVER_NAME); + dump_state(dev); + + the_controller = NULL; + + clrbits_le32(CKEN, CKEN11_USB); + + return 0; +} + +extern void udc_disconnect(void) +{ + setbits_le32(CKEN, CKEN11_USB); + udc_clear_mask_UDCCR(UDCCR_UDE); + udc_command(PXA2XX_UDC_CMD_DISCONNECT); + clrbits_le32(CKEN, CKEN11_USB); +} + +/*-------------------------------------------------------------------------*/ + +extern int +usb_gadget_handle_interrupts(void) +{ + return pxa25x_udc_irq(); +} diff --git a/drivers/usb/gadget/pxa25x_udc.h b/drivers/usb/gadget/pxa25x_udc.h new file mode 100644 index 0000000..de28a69 --- /dev/null +++ b/drivers/usb/gadget/pxa25x_udc.h @@ -0,0 +1,162 @@ +/* + * Intel PXA25x on-chip full speed USB device controller + * + * Copyright (C) 2003 Robert Schwebel r.schwebel@pengutronix.de, Pengutronix + * Copyright (C) 2003 David Brownell + * Copyright (C) 2012 Lukasz Dalek luk0104@gmail.com + * + * 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 __LINUX_USB_GADGET_PXA25X_H +#define __LINUX_USB_GADGET_PXA25X_H + +#include <linux/types.h> +#include <asm/arch/regs-usb.h> + +/* + * Prefetching support - only ARMv5. + */ + +#ifdef ARCH_HAS_PREFETCH +static inline void prefetch(const void *ptr) +{ + __asm__ __volatile__( + "pld\t%a0" + : + : "p" (ptr) + : "cc"); +} + +#define prefetchw(ptr) prefetch(ptr) +#endif /* ARCH_HAS_PREFETCH */ + +/*-------------------------------------------------------------------------*/ + +#define UDC_REGS ((struct pxa25x_udc_regs *)PXA25X_UDC_BASE) + +/*-------------------------------------------------------------------------*/ + +struct pxa2xx_udc_mach_info { + int (*udc_is_connected)(void); /* do we see host? */ + void (*udc_command)(int cmd); +#define PXA2XX_UDC_CMD_CONNECT 0 /* let host see us */ +#define PXA2XX_UDC_CMD_DISCONNECT 1 /* so host won't see us */ +}; + +struct pxa25x_udc; + +struct pxa25x_ep { + struct usb_ep ep; + struct pxa25x_udc *dev; + + const struct usb_endpoint_descriptor *desc; + struct list_head queue; + unsigned long pio_irqs; + + unsigned short fifo_size; + u8 bEndpointAddress; + u8 bmAttributes; + + unsigned stopped:1; + + /* UDCCS = UDC Control/Status for this EP + * UBCR = UDC Byte Count Remaining (contents of OUT fifo) + * UDDR = UDC Endpoint Data Register (the fifo) + * DRCM = DMA Request Channel Map + */ + u32 *reg_udccs; + u32 *reg_ubcr; + u32 *reg_uddr; +}; + +struct pxa25x_request { + struct usb_request req; + struct list_head queue; +}; + +enum ep0_state { + EP0_IDLE, + EP0_IN_DATA_PHASE, + EP0_OUT_DATA_PHASE, + EP0_END_XFER, + EP0_STALL, +}; + +#define EP0_FIFO_SIZE 16U +#define BULK_FIFO_SIZE 64U +#define ISO_FIFO_SIZE 256U +#define INT_FIFO_SIZE 8U + +struct udc_stats { + struct ep0stats { + unsigned long ops; + unsigned long bytes; + } read, write; + unsigned long irqs; +}; + +#ifdef CONFIG_USB_PXA25X_SMALL +/* when memory's tight, SMALL config saves code+data. */ +#define PXA_UDC_NUM_ENDPOINTS 3 +#endif + +#ifndef PXA_UDC_NUM_ENDPOINTS +#define PXA_UDC_NUM_ENDPOINTS 16 +#endif + +struct pxa25x_watchdog { + unsigned running:1; + ulong period; + ulong base; + struct pxa25x_udc *udc; + + void (*function)(struct pxa25x_udc *udc); +}; + +struct pxa25x_udc { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct pxa25x_udc_regs *regs; + + enum ep0_state ep0state; + struct udc_stats stats; + unsigned got_irq:1, + pullup:1, + has_cfr:1, + req_pending:1, + req_std:1, + req_config:1, + active:1; + + struct clk *clk; + struct pxa2xx_udc_mach_info *mach; + u64 dma_mask; + struct pxa25x_ep ep[PXA_UDC_NUM_ENDPOINTS]; + + struct pxa25x_watchdog watchdog; +}; + +/*-------------------------------------------------------------------------*/ + +static struct pxa25x_udc *the_controller; + +/*-------------------------------------------------------------------------*/ + +#ifndef DEBUG +# define NOISY 0 +#endif + +#endif /* __LINUX_USB_GADGET_PXA25X_H */

PXA25x gadget implements common function usb_disconnect(). This patch adds this function prototype into usb.h for boards using it.
Signed-off-by: Lukasz Dalek luk0104@gmail.com --- include/usb.h | 11 +++++++++++ 1 files changed, 11 insertions(+), 0 deletions(-)
diff --git a/include/usb.h b/include/usb.h index ba3d169..b6642de 100644 --- a/include/usb.h +++ b/include/usb.h @@ -166,6 +166,17 @@ int submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer, #define USB_UHCI_VEND_ID 0x8086 #define USB_UHCI_DEV_ID 0x7112
+/* + * PXA25x can only act as USB device. There are drivers + * which works with USB CDC gadgets implementations. + * Some of them have common routines which can be used + * in boards init functions e.g. udc_disconnect() used for + * forced device disconnection from host. + */ +#elif defined(CONFIG_USB_GADGET_PXA2XX) + +extern void udc_disconnect(void); + #else #error USB Lowlevel not defined #endif

Add support for ethernet over USB which can be used for e.g. booting process. It works with tftp and dhcp clients code.
Signed-off-by: Lukasz Dalek luk0104@gmail.com --- board/h2200/h2200.c | 15 +++++++++++++++ boards.cfg | 1 + include/configs/h2200.h | 27 +++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 0 deletions(-)
diff --git a/board/h2200/h2200.c b/board/h2200/h2200.c index 3076306..c5b084e 100644 --- a/board/h2200/h2200.c +++ b/board/h2200/h2200.c @@ -22,9 +22,18 @@ #include <asm/arch/pxa.h> #include <asm/arch/pxa-regs.h> #include <asm/io.h> +#include <usb.h>
DECLARE_GLOBAL_DATA_PTR;
+#ifdef CONFIG_H2200_USBETH +int board_eth_init(bd_t *bis) +{ + usb_eth_initialize(bis); + return 0; +} +#endif + int board_init(void) { /* We have RAM, disable cache */ @@ -36,6 +45,12 @@ int board_init(void) /* adress of boot parameters */ gd->bd->bi_boot_params = 0xa0000100;
+ /* Let host see that device is disconnected */ +#if defined(CONFIG_H2200_USBETH) + udc_disconnect(); + mdelay(500); +#endif + return 0; }
diff --git a/boards.cfg b/boards.cfg index ab10938..a03a469 100644 --- a/boards.cfg +++ b/boards.cfg @@ -283,6 +283,7 @@ pdnb3 arm ixp pdnb3 prodriv scpu arm ixp pdnb3 prodrive - pdnb3:SCPU balloon3 arm pxa h2200 arm pxa +h2200_usbeth arm pxa h2200 - - h2200:H2200_USBETH lubbock arm pxa palmld arm pxa palmtc arm pxa diff --git a/include/configs/h2200.h b/include/configs/h2200.h index de5299b..4c5a1ae 100644 --- a/include/configs/h2200.h +++ b/include/configs/h2200.h @@ -153,4 +153,31 @@
#define CONFIG_BOOTARGS "root=/dev/ram0 ro console=ttyS0,115200n8"
+#ifdef CONFIG_H2200_USBETH +# define CONFIG_SYS_CONSOLE_IS_IN_ENV +# define CONFIG_USB_DEV_PULLUP_GPIO 33 +/* USB VBUS GPIO 3 */ + +# define CONFIG_CMD_NET +# define CONFIG_CMD_PING + +# define CONFIG_BOOTDELAY 2 +# define CONFIG_BOOTCOMMAND \ + "setenv downloaded 0 ; while test $downloaded -eq 0 ; do " \ + "if bootp ; then setenv downloaded 1 ; fi ; done ; " \ + "source :script ; " \ + "bootm ; " + +# define CONFIG_USB_GADGET_PXA2XX +# define CONFIG_USB_ETHER +# define CONFIG_USB_ETH_SUBSET + +# define CONFIG_USBNET_DEV_ADDR "de:ad:be:ef:00:01" +# define CONFIG_USBNET_HOST_ADDR "de:ad:be:ef:00:02" +# define CONFIG_EXTRA_ENV_SETTINGS \ + "stdin=serial\0" \ + "stdout=serial\0" \ + "stderr=serial\0" +#endif + #endif /* __CONFIG_H */

Dear Lukasz Dalek,
Add basic support for HP iPAQ h2200 palmtop. h2200 palmtop was targeted to general consumers. It has 64 MB of RAM, 32 MB flash. No intergrated Wi-Fi nor Ethernet. Based on Intel PXA255 processor. It was shipped with Windows CE 4.2 operating system.
Signed-off-by: Lukasz Dalek luk0104@gmail.com
board/h2200/Makefile | 51 ++++++++++++++ board/h2200/h2200-header.S | 27 ++++++++ board/h2200/h2200.c | 53 +++++++++++++++ boards.cfg | 1 + include/configs/h2200.h | 156 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 288 insertions(+), 0 deletions(-) create mode 100644 board/h2200/Makefile create mode 100644 board/h2200/h2200-header.S create mode 100644 board/h2200/h2200.c create mode 100644 include/configs/h2200.h
Will you please learn to write patch changelog ?
diff --git a/board/h2200/Makefile b/board/h2200/Makefile new file mode 100644 index 0000000..2265aa9 --- /dev/null +++ b/board/h2200/Makefile @@ -0,0 +1,51 @@ +# +# h2200 Support +# +# Copyright (C) 2012 Lukasz Dalek luk0104@gmail.com +# +# 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 $(TOPDIR)/config.mk
+LIB = $(obj)lib$(BOARD).o
+COBJS := h2200.o
+SRCS := $(COBJS:.o=.c) h2200-header.S +OBJS := $(addprefix $(obj),$(COBJS))
+ALL: $(LIB) h2200-header.bin
+h2200-header.o: h2200-header.S
- $(CC) $(CFLAGS) -c -o $@ $<
Try building with BUILD_DIR=<somewhere> ./MAKEALL h2200
hint: you're missing $(obj) all around here so the resulting files are still in the source tree, not the BUILD_DIR, which is wrong of course.
+h2200-header.bin: h2200-header.o
- $(OBJCOPY) -O binary $< $@
+all: $(LIB) h2200-header.bin
[...]

On 02.10.2012 21:35, Marek Vasut wrote:
Dear Lukasz Dalek,
Add basic support for HP iPAQ h2200 palmtop. h2200 palmtop was targeted to general consumers. It has 64 MB of RAM, 32 MB flash. No intergrated Wi-Fi nor Ethernet. Based on Intel PXA255 processor. It was shipped with Windows CE 4.2 operating system.
Signed-off-by: Lukasz Dalekluk0104@gmail.com
board/h2200/Makefile | 51 ++++++++++++++ board/h2200/h2200-header.S | 27 ++++++++ board/h2200/h2200.c | 53 +++++++++++++++ boards.cfg | 1 + include/configs/h2200.h | 156 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 288 insertions(+), 0 deletions(-) create mode 100644 board/h2200/Makefile create mode 100644 board/h2200/h2200-header.S create mode 100644 board/h2200/h2200.c create mode 100644 include/configs/h2200.h
Will you please learn to write patch changelog ?
I will, I promise.
diff --git a/board/h2200/Makefile b/board/h2200/Makefile new file mode 100644 index 0000000..2265aa9 --- /dev/null +++ b/board/h2200/Makefile @@ -0,0 +1,51 @@ +# +# h2200 Support +# +# Copyright (C) 2012 Lukasz Dalekluk0104@gmail.com +# +# 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 $(TOPDIR)/config.mk
+LIB = $(obj)lib$(BOARD).o
+COBJS := h2200.o
+SRCS := $(COBJS:.o=.c) h2200-header.S +OBJS := $(addprefix $(obj),$(COBJS))
+ALL: $(LIB) h2200-header.bin
+h2200-header.o: h2200-header.S
- $(CC) $(CFLAGS) -c -o $@ $<
Try building with BUILD_DIR=<somewhere> ./MAKEALL h2200
what toolchain do you recommend? I use codesourcery and it complains about some enums.
hint: you're missing $(obj) all around here so the resulting files are still in the source tree, not the BUILD_DIR, which is wrong of course.
Can you recommend board which has proper Makefile?
Łukasz Dałek

Dear Łukasz Dałek,
On 02.10.2012 21:35, Marek Vasut wrote:
Dear Lukasz Dalek,
Add basic support for HP iPAQ h2200 palmtop. h2200 palmtop was targeted to general consumers. It has 64 MB of RAM, 32 MB flash. No intergrated Wi-Fi nor Ethernet. Based on Intel PXA255 processor. It was shipped with Windows CE 4.2 operating system.
Signed-off-by: Lukasz Dalekluk0104@gmail.com
board/h2200/Makefile | 51 ++++++++++++++ board/h2200/h2200-header.S | 27 ++++++++ board/h2200/h2200.c | 53 +++++++++++++++ boards.cfg | 1 + include/configs/h2200.h | 156
++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 288 insertions(+), 0 deletions(-)
create mode 100644 board/h2200/Makefile create mode 100644 board/h2200/h2200-header.S create mode 100644 board/h2200/h2200.c create mode 100644 include/configs/h2200.h
Will you please learn to write patch changelog ?
I will, I promise.
diff --git a/board/h2200/Makefile b/board/h2200/Makefile new file mode 100644 index 0000000..2265aa9 --- /dev/null +++ b/board/h2200/Makefile @@ -0,0 +1,51 @@ +# +# h2200 Support +# +# Copyright (C) 2012 Lukasz Dalekluk0104@gmail.com +# +# 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 $(TOPDIR)/config.mk
+LIB = $(obj)lib$(BOARD).o
+COBJS := h2200.o
+SRCS := $(COBJS:.o=.c) h2200-header.S +OBJS := $(addprefix $(obj),$(COBJS))
+ALL: $(LIB) h2200-header.bin
+h2200-header.o: h2200-header.S
- $(CC) $(CFLAGS) -c -o $@ $<
Try building with BUILD_DIR=<somewhere> ./MAKEALL h2200
what toolchain do you recommend? I use codesourcery and it complains about some enums.
http://www.denx.de/wiki/DULG/ELDK or debian toolchains
hint: you're missing $(obj) all around here so the resulting files are still in the source tree, not the BUILD_DIR, which is wrong of course.
Can you recommend board which has proper Makefile?
All of them should, did you detect such problem elsewhere?
Łukasz Dałek
Best regards, Marek Vasut

On 02.10.2012 22:19, Marek Vasut wrote:
hint: you're missing $(obj) all around here so the resulting files are still in the source tree, not the BUILD_DIR, which is wrong of course.
Can you recommend board which has proper Makefile?
All of them should, did you detect such problem elsewhere?
No. I'll fix this.
Łukasz Dałek
participants (3)
-
Lukasz Dalek
-
Marek Vasut
-
Łukasz Dałek