* [PATCH] powerpc: fix compile fail in hugetlb cmdline parsing
From: Paul Gortmaker @ 2012-05-07 14:32 UTC (permalink / raw)
To: linuxppc-dev
Cc: Jim Cromie, Greg Kroah-Hartman, Jason Baron, Rusty Russell,
Paul Gortmaker, linux-next
Commit 9fb48c744ba6a4bf58b666f4e6fdac3008ea1bd4
"params: add 3rd arg to option handler callback signature"
added an extra arg to the function, but didn't catch all the use
cases needing it, causing this compile fail in mpc85xx_defconfig:
arch/powerpc/mm/hugetlbpage.c:316:4: error: passing argument 7 of
'parse_args' from incompatible pointer type [-Werror]
include/linux/moduleparam.h:317:12: note: expected
'int (*)(char *, char *, const char *)' but argument is of type
'int (*)(char *, char *)'
This function has no need to printk out the "doing" value, so
just add the arg as an "unused".
Cc: Rusty Russell <rusty@rustcorp.com.au>
Cc: Jim Cromie <jim.cromie@gmail.com>
Cc: Jason Baron <jbaron@redhat.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Becky Bruce <beckyb@kernel.crashing.org>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Paul Gortmaker <paul.gortmaker@windriver.com>
diff --git a/arch/powerpc/mm/hugetlbpage.c b/arch/powerpc/mm/hugetlbpage.c
index fb05b12..1a6de0a 100644
--- a/arch/powerpc/mm/hugetlbpage.c
+++ b/arch/powerpc/mm/hugetlbpage.c
@@ -271,7 +271,8 @@ int alloc_bootmem_huge_page(struct hstate *hstate)
unsigned long gpage_npages[MMU_PAGE_COUNT];
-static int __init do_gpage_early_setup(char *param, char *val)
+static int __init do_gpage_early_setup(char *param, char *val,
+ const char *unused)
{
static phys_addr_t size;
unsigned long npages;
--
1.7.9.1
^ permalink raw reply related
* Re: [PATCH EDACv16 1/2] edac: Change internal representation to work with layers, second version
From: Borislav Petkov @ 2012-05-07 13:32 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Shaohui Xie, Jason Uhlenkott, Aristeu Rozanski, Hitoshi Mitake,
Mark Gross, Dmitry Eremin-Solenikov, Ranganathan Desikan,
Borislav Petkov, Egor Martovetsky, Niklas Söderlund,
Tim Small, Arvind R., Chris Metcalf, Olof Johansson,
Doug Thompson, Linux Edac Mailing List, Michal Marek, Jiri Kosina,
Linux Kernel Mailing List, Joe Perches, Andrew Morton,
linuxppc-dev
breaking thread because it grew too big.
On Fri, May 04, 2012 at 07:48:42AM -0300, Mauro Carvalho Chehab wrote:
[ … ]
> + memset(&pos, 0, sizeof(pos));
> + row = 0;
> + chn = 0;
> + debugf4("%s: initializing %d %s\n", __func__, tot_dimms,
> + per_rank ? "ranks" : "dimms");
> + for (i = 0; i < tot_dimms; i++) {
> + chan = &csi[row].channels[chn];
> + dimm = EDAC_DIMM_PTR(layer, mci->dimms, n_layers,
> + pos[0], pos[1], pos[2]);
> + dimm->mci = mci;
> +
> + debugf2("%s: %d: %s%zd (%d:%d:%d): row %d, chan %d\n", __func__,
> + i, per_rank ? "rank" : "dimm", (dimm - mci->dimms),
> + pos[0], pos[1], pos[2], row, chn);
> +
> + /* Copy DIMM location */
> + for (j = 0; j < n_layers; j++)
> + dimm->location[j] = pos[j];
> +
> + /* Link it to the csrows old API data */
> + chan->dimm = dimm;
> + dimm->csrow = row;
> + dimm->cschannel = chn;
> +
> + /* Increment csrow location */
> + for (j = n_layers - 1; j >= 0; j--)
> + if (layers[j].is_virt_csrow)
> + break;
This looks fishy: the for-loop above iterates over layers[j] to break on
the first ->is_virt_csrow.
> + row++;
And row here gets incremented unconditionally, independent from the loop
above. And you're not using any results from the loop: j gets reset
below in the next loop.
What's going on?
> + if (row == tot_csrows) {
> + row = 0;
> + chn++;
> + }
> +
> + /* Increment dimm location */
> + for (j = n_layers - 1; j >= 0; j--) {
> + pos[j]++;
> + if (pos[j] < layers[j].size)
> + break;
> + pos[j] = 0;
> }
> }
>
> @@ -263,6 +373,46 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
> */
> return mci;
> }
> +EXPORT_SYMBOL_GPL(new_edac_mc_alloc);
> +
> +/**
> + * edac_mc_alloc: Allocate and partially fill a struct mem_ctl_info structure
> + * @mc_num: Memory controller number
> + * @n_layers: Number of layers at the MC hierarchy
> + * layers: Describes each layer as seen by the Memory Controller
> + * @size_pvt: Size of private storage needed
> + *
> + *
> + * FIXME: drivers handle multi-rank memories in different ways: some
> + * drivers map multi-ranked DIMMs as one DIMM while others
> + * as several DIMMs.
> + *
> + * Everything is kmalloc'ed as one big chunk - more efficient.
> + * It can only be used if all structures have the same lifetime - otherwise
> + * you have to allocate and initialize your own structures.
> + *
> + * Use edac_mc_free() to free mc structures allocated by this function.
> + *
> + * Returns:
> + * On failure: NULL
> + * On success: struct mem_ctl_info pointer
> + */
> +
[ … ]
> +void edac_mc_handle_error(const enum hw_event_mc_err_type type,
> + struct mem_ctl_info *mci,
> + const unsigned long page_frame_number,
> + const unsigned long offset_in_page,
> + const unsigned long syndrome,
> + const int layer0,
> + const int layer1,
> + const int layer2,
> + const char *msg,
> + const char *other_detail,
> + const void *mcelog)
> {
> - int len = EDAC_MC_LABEL_LEN * 4;
> - char labels[len + 1];
> - char *pos = labels;
> - int chan;
> - int chars;
> - char *label = NULL;
> + /* FIXME: too much for stack: move it to some pre-alocated area */
I'm assuming all those new FIXMEs are going to be addressed soonish :)
Rest looks ok,
thanks.
--
Regards/Gruss,
Boris.
Advanced Micro Devices GmbH
Einsteinring 24, 85609 Dornach
GM: Alberto Bozzo
Reg: Dornach, Landkreis Muenchen
HRB Nr. 43632 WEEE Registernr: 129 19551
^ permalink raw reply
* [PATCH] powerpc/44x: Support OCM(On Chip Memory) for APM821xx SoC and Bluestone board
From: Vinh Nguyen Huu Tuong @ 2012-05-07 3:52 UTC (permalink / raw)
To: Benjamin Herrenschmidt, Paul Mackerras, Josh Boyer, Matt Porter,
Grant Likely, Rob Herring, Duc Dang, David S. Miller, Kumar Gala,
Li Yang, Ashish Kalra, Anatolij Gustschin, Liu Gang, linuxppc-dev,
linux-kernel, devicetree-discuss
Cc: Vinh Nguyen Huu Tuong
This patch consists of:
- Add driver for OCM component
- Export OCM Information at /sys/class/ocm/ocminfo
Signed-off-by: Vinh Nguyen Huu Tuong <vhtnguyen@apm.com>
---
arch/powerpc/boot/dts/bluestone.dts | 8 +
arch/powerpc/include/asm/ppc4xx_ocm.h | 47 ++++
arch/powerpc/platforms/44x/Kconfig | 8 +
arch/powerpc/sysdev/Makefile | 1 +
arch/powerpc/sysdev/ppc4xx_ocm.c | 420 +++++++++++++++++++++++++++++++++
5 files changed, 484 insertions(+), 0 deletions(-)
create mode 100644 arch/powerpc/include/asm/ppc4xx_ocm.h
create mode 100644 arch/powerpc/sysdev/ppc4xx_ocm.c
diff --git a/arch/powerpc/boot/dts/bluestone.dts b/arch/powerpc/boot/dts/bluestone.dts
index 7bda373..2687c11 100644
--- a/arch/powerpc/boot/dts/bluestone.dts
+++ b/arch/powerpc/boot/dts/bluestone.dts
@@ -107,6 +107,14 @@
interrupt-parent = <&UIC0>;
};
+ OCM1: ocm@400040000 {
+ compatible = "ibm,ocm";
+ status = "ok";
+ cell-index = <1>;
+ /* configured in U-Boot */
+ reg = <4 0x00040000 0x8000>; /* 32K */
+ };
+
SDR0: sdr {
compatible = "ibm,sdr-apm821xx";
dcr-reg = <0x00e 0x002>;
diff --git a/arch/powerpc/include/asm/ppc4xx_ocm.h b/arch/powerpc/include/asm/ppc4xx_ocm.h
new file mode 100644
index 0000000..ff7f386
--- /dev/null
+++ b/arch/powerpc/include/asm/ppc4xx_ocm.h
@@ -0,0 +1,47 @@
+/*
+ * PowerPC 4xx OCM memory allocation support
+ *
+ * (C) Copyright 2009, Applied Micro Circuits Corporation
+ * Victor Gallardo (vgallardo@amcc.com)
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * 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 __ASM_POWERPC_PPC4xx_OCM_H__
+#define __ASM_POWERPC_PPC4xx_OCM_H__
+
+#include <linux/types.h>
+
+#define OCM_NON_CACHED 0
+#define OCM_CACHED 1
+
+#if defined(CONFIG_PPC4xx_OCM)
+
+void *ocm_alloc(phys_addr_t *phys, int size, int align,
+ int flags, const char *owner);
+void ocm_free(const void *virt);
+
+#else
+
+#define ocm_alloc(phys, size, align, flags, owner) NULL
+#define ocm_free(addr) ((void)0)
+
+#endif /* CONFIG_PPC4xx_OCM */
+
+#endif /* __ASM_POWERPC_PPC4xx_OCM_H__ */
diff --git a/arch/powerpc/platforms/44x/Kconfig b/arch/powerpc/platforms/44x/Kconfig
index 2e4e64a..6b1a64e 100644
--- a/arch/powerpc/platforms/44x/Kconfig
+++ b/arch/powerpc/platforms/44x/Kconfig
@@ -250,6 +250,14 @@ config PPC4xx_GPIO
help
Enable gpiolib support for ppc440 based boards
+config PPC4xx_OCM
+ bool "PPC4xx On Chip Memory (OCM) support"
+ depends on 4xx
+ select PPC_LIB_RHEAP
+ help
+ Enable OCM support for PowerPC 4xx platforms with on chip memory,
+ OCM provides the fast place for memory access to improve performance.
+
# 44x specific CPU modules, selected based on the board above.
config 440EP
bool
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index 1bd7ecb..6f768e2 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_PPC_INDIRECT_PCI) += indirect_pci.o
obj-$(CONFIG_PPC_I8259) += i8259.o
obj-$(CONFIG_IPIC) += ipic.o
obj-$(CONFIG_4xx) += uic.o
+obj-$(CONFIG_PPC4xx_OCM) += ppc4xx_ocm.o
obj-$(CONFIG_4xx_SOC) += ppc4xx_soc.o
obj-$(CONFIG_XILINX_VIRTEX) += xilinx_intc.o
obj-$(CONFIG_XILINX_PCI) += xilinx_pci.o
diff --git a/arch/powerpc/sysdev/ppc4xx_ocm.c b/arch/powerpc/sysdev/ppc4xx_ocm.c
new file mode 100644
index 0000000..ba3e450
--- /dev/null
+++ b/arch/powerpc/sysdev/ppc4xx_ocm.c
@@ -0,0 +1,420 @@
+/*
+ * PowerPC 4xx OCM memory allocation support
+ *
+ * (C) Copyright 2009, Applied Micro Circuits Corporation
+ * Victor Gallardo (vgallardo@amcc.com)
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * 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 <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/spinlock.h>
+#include <linux/dma-mapping.h>
+#include <linux/list.h>
+#include <asm/uaccess.h>
+#include <asm/prom.h>
+#include <asm/dcr.h>
+#include <asm/rheap.h>
+#include <asm/mmu.h>
+#include <asm/ppc4xx_ocm.h>
+#include <linux/export.h>
+
+#define OCM_DISABLED 0
+#define OCM_ENABLED 1
+
+struct ocm_block {
+ struct list_head list;
+ void __iomem *addr;
+ int size;
+ const char *owner;
+};
+
+/* non-cached or cached region */
+struct ocm_region {
+ phys_addr_t phys;
+ void __iomem *virt;
+
+ int memtotal;
+ int memfree;
+
+ rh_info_t *rh;
+ struct list_head list;
+};
+
+struct ocm_info {
+ int index;
+ int status;
+ int ready;
+
+ phys_addr_t phys;
+
+ int alignment;
+ int memtotal;
+ int cache_size;
+
+ struct ocm_region nc; /* non-cached region */
+ struct ocm_region c; /* cached region */
+};
+
+static struct ocm_info *ocm_nodes;
+static int ocm_count;
+
+static struct ocm_info *ocm_get_node(unsigned int index)
+{
+ if (index >= ocm_count) {
+ printk(KERN_ERR "OCM: invalid index");
+ return NULL;
+ }
+
+ return &ocm_nodes[index];
+}
+
+void *ocm_alloc(phys_addr_t *phys, int size, int align,
+ int flags, const char *owner)
+{
+ void __iomem *addr = NULL;
+ unsigned long offset;
+ struct ocm_info *ocm;
+ struct ocm_region *ocm_reg;
+ struct ocm_block *ocm_blk;
+ int i;
+
+ for (i = 0; i < ocm_count; i++) {
+ ocm = ocm_get_node(i);
+
+ if (!ocm || !ocm->ready)
+ continue;
+
+ if (flags == OCM_NON_CACHED)
+ ocm_reg = &ocm->nc;
+ else
+ ocm_reg = &ocm->c;
+
+ if (!ocm_reg->virt)
+ continue;
+
+ if (align < ocm->alignment)
+ align = ocm->alignment;
+
+ offset = rh_alloc_align(ocm_reg->rh, size, align, NULL);
+
+ if (IS_ERR_VALUE(offset))
+ continue;
+
+ ocm_blk = kzalloc(sizeof(struct ocm_block *), GFP_KERNEL);
+ if (!ocm_blk) {
+ printk(KERN_ERR "OCM: could not allocate ocm block");
+ rh_free(ocm_reg->rh, offset);
+ break;
+ }
+
+ *phys = ocm_reg->phys + offset;
+ addr = ocm_reg->virt + offset;
+ size = ALIGN(size, align);
+
+ ocm_blk->addr = addr;
+ ocm_blk->size = size;
+ ocm_blk->owner = owner;
+ list_add_tail(&ocm_blk->list, &ocm_reg->list);
+
+ ocm_reg->memfree -= size;
+
+ break;
+ }
+
+ return addr;
+}
+
+static int ocm_free_region(struct ocm_region *ocm_reg, const void *addr)
+{
+ struct ocm_block *blk, *tmp;
+ unsigned long offset;
+
+ if (!ocm_reg->virt)
+ return 0;
+
+ list_for_each_entry_safe(blk, tmp, &ocm_reg->list, list) {
+ if (blk->addr == addr) {
+ offset = addr - ocm_reg->virt;
+ ocm_reg->memfree += blk->size;
+ rh_free(ocm_reg->rh, offset);
+ list_del(&blk->list);
+ kfree(blk);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+void ocm_free(const void *addr)
+{
+ int i;
+
+ if (!addr)
+ return;
+
+ for (i = 0; i < ocm_count; i++) {
+ struct ocm_info *ocm = ocm_get_node(i);
+
+ if (!ocm || !ocm->ready)
+ continue;
+
+ if (ocm_free_region(&ocm->nc, addr) ||
+ ocm_free_region(&ocm->c, addr))
+ return;
+ }
+}
+
+static void __init ocm_init_node(int count, struct device_node *node)
+{
+ struct ocm_info *ocm;
+
+ const unsigned int *cell_index;
+ const unsigned int *cache_size;
+ int len;
+
+ struct resource rsrc;
+ int ioflags;
+
+ ocm = ocm_get_node(count);
+
+ cell_index = of_get_property(node, "cell-index", &len);
+ if (!cell_index) {
+ printk(KERN_ERR "OCM: missing cell-index property");
+ return;
+ }
+ ocm->index = *cell_index;
+
+ if (of_device_is_available(node))
+ ocm->status = OCM_ENABLED;
+
+ cache_size = of_get_property(node, "cached-region-size", &len);
+ if (cache_size)
+ ocm->cache_size = *cache_size;
+
+ if (of_address_to_resource(node, 0, &rsrc)) {
+ printk(KERN_ERR "OCM%d: could not get resource address\n",
+ ocm->index);
+ return;
+ }
+
+ ocm->phys = rsrc.start;
+ ocm->memtotal = (rsrc.end - rsrc.start + 1);
+
+ printk(KERN_INFO "OCM%d: %d Bytes (%s)\n",
+ ocm->index, ocm->memtotal,
+ (ocm->status == OCM_DISABLED) ? "disabled" : "enabled");
+
+ if (ocm->status == OCM_DISABLED)
+ return;
+
+ /* request region */
+
+ if (!request_mem_region(ocm->phys, ocm->memtotal, "ppc4xx_ocm")) {
+ printk(KERN_ERR "OCM%d: could not request region\n",
+ ocm->index);
+ return;
+ }
+
+ /* Configure non-cached and cached regions */
+
+ ocm->nc.phys = ocm->phys;
+ ocm->nc.memtotal = ocm->memtotal - ocm->cache_size;
+ ocm->nc.memfree = ocm->nc.memtotal;
+
+ ocm->c.phys = ocm->phys + ocm->nc.memtotal;
+ ocm->c.memtotal = ocm->cache_size;
+ ocm->c.memfree = ocm->c.memtotal;
+
+ if (ocm->nc.memtotal == 0)
+ ocm->nc.phys = 0;
+
+ if (ocm->c.memtotal == 0)
+ ocm->c.phys = 0;
+
+ printk(KERN_INFO "OCM%d: %d Bytes (non-cached)\n",
+ ocm->index, ocm->nc.memtotal);
+
+ printk(KERN_INFO "OCM%d: %d Bytes (cached)\n",
+ ocm->index, ocm->c.memtotal);
+
+ /* ioremap the non-cached region */
+ if (ocm->nc.memtotal) {
+ ioflags = _PAGE_NO_CACHE | _PAGE_GUARDED | _PAGE_EXEC;
+ ocm->nc.virt = __ioremap(ocm->nc.phys, ocm->nc.memtotal,
+ ioflags);
+
+ if (!ocm->nc.virt) {
+ printk(KERN_ERR
+ "OCM%d: failed to ioremap non-cached memory\n",
+ ocm->index);
+ ocm->nc.memfree = 0;
+ return;
+ }
+ }
+
+ /* ioremap the cached region */
+
+ if (ocm->c.memtotal) {
+ ioflags = _PAGE_EXEC;
+ ocm->c.virt = __ioremap(ocm->c.phys, ocm->c.memtotal,
+ ioflags);
+
+ if (!ocm->c.virt) {
+ printk(KERN_ERR
+ "OCM%d: failed to ioremap cached memory\n",
+ ocm->index);
+ ocm->c.memfree = 0;
+ return;
+ }
+ }
+
+ /* Create Remote Heaps */
+
+ ocm->alignment = 4; /* default 4 byte alignment */
+
+ if (ocm->nc.virt) {
+ ocm->nc.rh = rh_create(ocm->alignment);
+ rh_attach_region(ocm->nc.rh, 0, ocm->nc.memtotal);
+ }
+
+ if (ocm->c.virt) {
+ ocm->c.rh = rh_create(ocm->alignment);
+ rh_attach_region(ocm->c.rh, 0, ocm->c.memtotal);
+ }
+
+ INIT_LIST_HEAD(&ocm->nc.list);
+ INIT_LIST_HEAD(&ocm->c.list);
+
+ ocm->ready = 1;
+
+ return;
+}
+
+static ssize_t ocm_sysfs_show(struct class *class,
+ struct class_attribute *attr, char *buf)
+{
+ struct ocm_block *blk, *tmp;
+ unsigned int count, i;
+
+ count = 0;
+ for (i = 0; i < ocm_count; i++) {
+ struct ocm_info *ocm = ocm_get_node(i);
+
+ if (!ocm || !ocm->ready)
+ continue;
+
+ count += sprintf(buf + count, "OCM : %d\n",
+ ocm->index);
+ count += sprintf(buf + count, "PhysAddr : 0x%llx\n",
+ ocm->phys);
+ count += sprintf(buf + count, "MemTotal : %d Bytes\n",
+ ocm->memtotal);
+ count += sprintf(buf + count, "MemTotal(NC) : %d Bytes\n",
+ ocm->nc.memtotal);
+ count += sprintf(buf + count, "MemTotal(C) : %d Bytes\n",
+ ocm->c.memtotal);
+
+ count += sprintf(buf + count, "\n");
+
+ count += sprintf(buf + count, "NC.PhysAddr : 0x%llx\n",
+ ocm->nc.phys);
+ count += sprintf(buf + count, "NC.VirtAddr : 0x%p\n",
+ ocm->nc.virt);
+ count += sprintf(buf + count, "NC.MemTotal : %d Bytes\n",
+ ocm->nc.memtotal);
+ count += sprintf(buf + count, "NC.MemFree : %d Bytes\n",
+ ocm->nc.memfree);
+
+ list_for_each_entry_safe(blk, tmp, &ocm->nc.list, list) {
+ count += sprintf(buf + count, "NC.MemUsed : %d Bytes (%s)\n",
+ blk->size, blk->owner);
+ }
+
+ count += sprintf(buf + count, "\n");
+
+ count += sprintf(buf + count, "C.PhysAddr : 0x%llx\n",
+ ocm->c.phys);
+ count += sprintf(buf + count, "C.VirtAddr : 0x%p\n",
+ ocm->c.virt);
+ count += sprintf(buf + count, "C.MemTotal : %d Bytes\n",
+ ocm->c.memtotal);
+ count += sprintf(buf + count, "C.MemFree : %d Bytes\n",
+ ocm->c.memfree);
+
+ list_for_each_entry_safe(blk, tmp, &ocm->c.list, list) {
+ count += sprintf(buf + count, "C.MemUsed : %d Bytes (%s)\n",
+ blk->size, blk->owner);
+ }
+
+ count += sprintf(buf + count, "\n");
+ }
+
+ return count;
+}
+
+static struct class *mem_class;
+
+static CLASS_ATTR(ocminfo, S_IRUGO, ocm_sysfs_show, NULL);
+
+static int ocm_sysfs_init(void)
+{
+ mem_class = class_create(THIS_MODULE, "ocm");
+ if (IS_ERR(mem_class))
+ return PTR_ERR(mem_class);
+
+ return class_create_file(mem_class, &class_attr_ocminfo);
+}
+
+static int __init ocm_init(void)
+{
+ struct device_node *np;
+ int count;
+
+ count = 0;
+ for_each_compatible_node(np, NULL, "ibm,ocm")
+ count++;
+
+ if (!count)
+ return 0;
+
+ ocm_nodes = kzalloc((count * sizeof(struct ocm_info)), GFP_KERNEL);
+ if (!ocm_nodes) {
+ printk(KERN_ERR "OCM: failed to allocate OCM nodes!\n");
+ return -ENOMEM;
+ }
+
+ ocm_count = count;
+ count = 0;
+
+ for_each_compatible_node(np, NULL, "ibm,ocm") {
+ ocm_init_node(count, np);
+ count++;
+ }
+
+ ocm_sysfs_init();
+
+ return 0;
+}
+
+arch_initcall(ocm_init);
--
1.7.2.5
^ permalink raw reply related
* Re: kilauea compilation breaks with v3.3 kernel and ELDK 4.2
From: Robert Berger @ 2012-05-06 14:37 UTC (permalink / raw)
To: Tony Breeds
Cc: linuxppc-dev, Wolfgang Denk, "Frank E. Svendsbøe"
In-Reply-To: <20120402062826.GC2129@thor.bakeyournoodle.com>
On 04/02/2012 09:28 AM, Tony Breeds wrote:
> On Mon, Apr 02, 2012 at 12:01:55PM +1000, Benjamin Herrenschmidt wrote:
>
>> Ok, I've asked Tony to have a look at splitting the build decision
>> in arch/powerpc/boot along the same lines as the CPU families... ie only
>> wrappers for platforms potentially supported by the built kernel. That
>> should fix it.
>
> Please try this patch. Only lightly tested here. I haven't "split up"
> src-wlib yet as I wanted to verify I'm on the right track.
I can confirm that it fixes the kilauea problem. Builds and runs so far.
Are you going to push this upstream?
Regards,
Robert
..."Software is like sex, it's better when it's free"
My public pgp key is available at:
http://pgp.mit.edu:11371/pks/lookup?op=get&search=0x90320BF1
^ permalink raw reply
* [PATCH] cpu: Document clear_tasks_mm_cpumask()
From: Anton Vorontsov @ 2012-05-05 1:47 UTC (permalink / raw)
To: Andrew Morton, Peter Zijlstra
Cc: linaro-kernel, Mike Frysinger, user-mode-linux-devel, linux-sh,
Richard Weinberger, linuxppc-dev, Oleg Nesterov, linux-kernel,
linux-mm, Paul Mundt, John Stultz, patches, KOSAKI Motohiro,
Russell King, uclinux-dist-devel, linux-arm-kernel
In-Reply-To: <1335869133.13683.125.camel@twins>
This patch adds more comments on clear_tasks_mm_cpumask, plus adds
a runtime check: the function is only suitable for offlined CPUs,
and if called inappropriately, the kernel should scream aloud.
Suggested-by: Andrew Morton <akpm@linux-foundation.org>
Suggested-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Signed-off-by: Anton Vorontsov <anton.vorontsov@linaro.org>
---
On Tue, May 01, 2012 at 12:45:33PM +0200, Peter Zijlstra wrote:
> On Thu, 2012-04-26 at 16:59 -0700, Andrew Morton wrote:
> > > +void clear_tasks_mm_cpumask(int cpu)
> >
> > The operation of this function was presumably obvious to you at the
> > time you wrote it, but that isn't true of other people at later times.
> >
> > Please document it?
[...]
> > If someone tries to use this function for a different purpose, or
> > copies-and-modifies it for a different purpose, we just shot them in
> > the foot.
> >
> > They'd be pretty dumb to do that without reading the local comment,
> > but still...
>
> Methinks something simple like:
>
> WARN_ON(cpu_online(cpu));
>
> Ought to cure that worry, no? :-)
Yeah, this is all good ideas, thanks.
How about the following patch?
kernel/cpu.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/kernel/cpu.c b/kernel/cpu.c
index ecdf499..1bfa26f 100644
--- a/kernel/cpu.c
+++ b/kernel/cpu.c
@@ -13,6 +13,7 @@
#include <linux/oom.h>
#include <linux/rcupdate.h>
#include <linux/export.h>
+#include <linux/bug.h>
#include <linux/kthread.h>
#include <linux/stop_machine.h>
#include <linux/mutex.h>
@@ -173,6 +174,18 @@ void __ref unregister_cpu_notifier(struct notifier_block *nb)
}
EXPORT_SYMBOL(unregister_cpu_notifier);
+/**
+ * clear_tasks_mm_cpumask - Safely clear tasks' mm_cpumask for a CPU
+ * @cpu: a CPU id
+ *
+ * This function walks up all processes, finds a valid mm struct for
+ * each one and then clears a corresponding bit in mm's cpumask. While
+ * this all sounds trivial, there are various non-obvious corner cases,
+ * which this function tries to solve in a safe manner.
+ *
+ * Also note that the function uses a somewhat relaxed locking scheme,
+ * so it may be called only for an already offlined CPU.
+ */
void clear_tasks_mm_cpumask(int cpu)
{
struct task_struct *p;
@@ -184,10 +197,15 @@ void clear_tasks_mm_cpumask(int cpu)
* Thus, we may use rcu_read_lock() here, instead of grabbing
* full-fledged tasklist_lock.
*/
+ WARN_ON(cpu_online(cpu));
rcu_read_lock();
for_each_process(p) {
struct task_struct *t;
+ /*
+ * Main thread might exit, but other threads may still have
+ * a valid mm. Find one.
+ */
t = find_lock_task_mm(p);
if (!t)
continue;
--
1.7.9.2
^ permalink raw reply related
* Re: [PATCH 1/9] cpu: Introduce clear_tasks_mm_cpumask() helper
From: Anton Vorontsov @ 2012-05-05 1:47 UTC (permalink / raw)
To: Andrew Morton
Cc: linaro-kernel, Mike Frysinger, Peter Zijlstra,
user-mode-linux-devel, linux-sh, Richard Weinberger, linuxppc-dev,
Oleg Nesterov, linux-kernel, linux-mm, Paul Mundt, John Stultz,
patches, KOSAKI Motohiro, Russell King, uclinux-dist-devel,
linux-arm-kernel
In-Reply-To: <20120426165911.00cebd31.akpm@linux-foundation.org>
On Thu, Apr 26, 2012 at 04:59:11PM -0700, Andrew Morton wrote:
[...]
> > so its not like new tasks will ever get this cpu set in
> > + * their mm mask. -- Peter Zijlstra
> > + * Thus, we may use rcu_read_lock() here, instead of grabbing
> > + * full-fledged tasklist_lock.
> > + */
> > + rcu_read_lock();
> > + for_each_process(p) {
> > + struct task_struct *t;
> > +
> > + t = find_lock_task_mm(p);
> > + if (!t)
> > + continue;
> > + cpumask_clear_cpu(cpu, mm_cpumask(t->mm));
> > + task_unlock(t);
> > + }
> > + rcu_read_unlock();
> > +}
>
> It is good that this code exists under CONFIG_HOTPLUG_CPU. Did you
> check that everything works correctly with CONFIG_HOTPLUG_CPU=n?
Yeah, only the code under CONFIG_HOTPLUG_CPU calls the function, so
it should be all fine.
Thanks!
--
Anton Vorontsov
Email: cbouatmailru@gmail.com
^ permalink raw reply
* Re: [PATCH EDACv16 1/2] edac: Change internal representation to work with layers, second version
From: Mauro Carvalho Chehab @ 2012-05-04 10:48 UTC (permalink / raw)
To: Borislav Petkov
Cc: Shaohui Xie, Jason Uhlenkott, Aristeu Rozanski, Hitoshi Mitake,
Mark Gross, Dmitry Eremin-Solenikov, Ranganathan Desikan,
Egor Martovetsky, Niklas Söderlund, Tim Small, Arvind R.,
Chris Metcalf, Olof Johansson, Doug Thompson,
Linux Edac Mailing List, Michal Marek, Jiri Kosina,
Linux Kernel Mailing List, Joe Perches, Andrew Morton,
linuxppc-dev
In-Reply-To: <20120504101636.GB18459@aftab.osrc.amd.com>
Em 04-05-2012 07:16, Borislav Petkov escreveu:
> On Thu, May 03, 2012 at 11:16:54AM -0300, Mauro Carvalho Chehab wrote:
>> edac: Change internal representation to work with layers
...
>> /**
>> - * edac_mc_alloc: Allocate a struct mem_ctl_info structure
>> - * @size_pvt: size of private storage needed
>> - * @nr_csrows: Number of CWROWS needed for this MC
>> - * @nr_chans: Number of channels for the MC
>> + * edac_mc_alloc: Allocate and partially fill a struct mem_ctl_info structure
>> + * @mc_num: Memory controller number
>> + * @n_layers: Number of MC hierarchy layers
>> + * layers: Describes each layer as seen by the Memory Controller
>> + * @size_pvt: size of private storage needed
>> + *
>> + *
>> + * Non-csrow based drivers (like FB-DIMM and RAMBUS ones) will likely report
>> + * such DIMMS properly, but the CSROWS-based ones will likely do the wrong
>
> DIMMs csrow-based
>
>> + * thing, as two chip select values are used for dual-rank memories (and 4, for
>> + * quad-rank ones). I suspect that this issue could be solved inside the EDAC
>> + * core for SDRAM memories, but it requires further study at JEDEC JESD 21C.
>
> The paragraph above is still in, let me repeat my last note:
>
> "This last paragraph sounds innacurately, especially the "likely"
> adverbs make it even more confusing. The gist of what you're saying is
> already present in the commit message anyway, so drop it here pls."
There are two similar comments one for edac_mc_alloc and another for new_edac_mc_alloc.
It seems that I fixed just one of them.
>
>> + *
>> + * In summary, solving this issue is not easy, as it requires a lot of testing.
>
> As before:
>
> "Also superfluous and has nothing to do with edac_mc_alloc()."
>
> Pls remove it.
Dropped both paragraphs.
>
>> * Everything is kmalloc'ed as one big chunk - more efficient.
>> * Only can be used if all structures have the same lifetime - otherwise
>> @@ -168,22 +196,49 @@ void *edac_align_ptr(void **p, unsigned size, int n_elems)
>> *
>> * Use edac_mc_free() to free mc structures allocated by this function.
>> *
>> + * NOTE: drivers handle multi-rank memories in different ways: in some
>> + * drivers, one multi-rank memory stick is mapped as one entry, while, in
>> + * others, a single multi-rank memory stick would be mapped into several
>> + * entries. Currently, this function will allocate multiple struct dimm_info
>> + * on such scenarios, as grouping the multiple ranks require drivers change.
>> + *
>> * Returns:
>> * NULL allocation failed
>> * struct mem_ctl_info pointer
>
> Ok, this patch still doesn't contain all the changes I requested for
> although you said you did them. Is this not the latest version? I'll
> wait for you to sort it out before I continue reviewing...
>
> Thanks.
>
Patch enclosed (and updated at Infradead).
Regards,
Mauro
-
edac: Change internal representation to work with layers
Change the EDAC internal representation to work with non-csrow
based memory controllers.
There are lots of those memory controllers nowadays, and more
are coming. So, the EDAC internal representation needs to be
changed, in order to work with those memory controllers, while
preserving backward compatibility with the old ones.
The edac core was written with the idea that memory controllers
are able to directly access csrows.
This is not true for FB-DIMM and RAMBUS memory controllers.
Also, some recent advanced memory controllers don't present a per-csrows
view. Instead, they view memories as DIMMs, instead of ranks.
So, change the allocation and error report routines to allow
them to work with all types of architectures.
This will allow the removal of several hacks with FB-DIMM and RAMBUS
memory controllers.
Also, several tests were done on different platforms using different
x86 drivers.
TODO: a multi-rank DIMMs are currently represented by multiple DIMM
entries in struct dimm_info. That means that changing a label for one
rank won't change the same label for the other ranks at the same DIMM.
This bug is present since the beginning of the EDAC, so it is not a big
deal. However, on several drivers, it is possible to fix this issue, but
it should be a per-driver fix, as the csrow => DIMM arrangement may not
be equal for all. So, don't try to fix it here yet.
I tried to make this patch as short as possible, preceding it with
several other patches that simplified the logic here. Yet, as the
internal API changes, all drivers need changes. The changes are
generally bigger in the drivers for FB-DIMMs.
Cc: Aristeu Rozanski <arozansk@redhat.com>
Cc: Doug Thompson <norsk5@yahoo.com>
Cc: Borislav Petkov <borislav.petkov@amd.com>
Cc: Mark Gross <mark.gross@intel.com>
Cc: Jason Uhlenkott <juhlenko@akamai.com>
Cc: Tim Small <tim@buttersideup.com>
Cc: Ranganathan Desikan <ravi@jetztechnologies.com>
Cc: "Arvind R." <arvino55@gmail.com>
Cc: Olof Johansson <olof@lixom.net>
Cc: Egor Martovetsky <egor@pasemi.com>
Cc: Chris Metcalf <cmetcalf@tilera.com>
Cc: Michal Marek <mmarek@suse.cz>
Cc: Jiri Kosina <jkosina@suse.cz>
Cc: Joe Perches <joe@perches.com>
Cc: Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Hitoshi Mitake <h.mitake@gmail.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: "Niklas Söderlund" <niklas.soderlund@ericsson.com>
Cc: Shaohui Xie <Shaohui.Xie@freescale.com>
Cc: Josh Boyer <jwboyer@gmail.com>
Cc: linuxppc-dev@lists.ozlabs.org
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
---
v19: dropped 2 comment paragraphs, as above.
diff --git a/drivers/edac/edac_core.h b/drivers/edac/edac_core.h
index e48ab31..1286c5e 100644
--- a/drivers/edac/edac_core.h
+++ b/drivers/edac/edac_core.h
@@ -447,8 +447,12 @@ static inline void pci_write_bits32(struct pci_dev *pdev, int offset,
#endif /* CONFIG_PCI */
-extern struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
- unsigned nr_chans, int edac_index);
+struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
+ unsigned nr_chans, int edac_index);
+struct mem_ctl_info *new_edac_mc_alloc(unsigned edac_index,
+ unsigned n_layers,
+ struct edac_mc_layer *layers,
+ unsigned sz_pvt);
extern int edac_mc_add_mc(struct mem_ctl_info *mci);
extern void edac_mc_free(struct mem_ctl_info *mci);
extern struct mem_ctl_info *edac_mc_find(int idx);
@@ -467,24 +471,78 @@ extern int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci,
* reporting logic and function interface - reduces conditional
* statement clutter and extra function arguments.
*/
-extern void edac_mc_handle_ce(struct mem_ctl_info *mci,
- unsigned long page_frame_number,
- unsigned long offset_in_page,
- unsigned long syndrome, int row, int channel,
- const char *msg);
-extern void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci,
- const char *msg);
-extern void edac_mc_handle_ue(struct mem_ctl_info *mci,
- unsigned long page_frame_number,
- unsigned long offset_in_page, int row,
- const char *msg);
-extern void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci,
- const char *msg);
-extern void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci, unsigned int csrow,
- unsigned int channel0, unsigned int channel1,
- char *msg);
-extern void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci, unsigned int csrow,
- unsigned int channel, char *msg);
+
+void edac_mc_handle_error(const enum hw_event_mc_err_type type,
+ struct mem_ctl_info *mci,
+ const unsigned long page_frame_number,
+ const unsigned long offset_in_page,
+ const unsigned long syndrome,
+ const int layer0,
+ const int layer1,
+ const int layer2,
+ const char *msg,
+ const char *other_detail,
+ const void *mcelog);
+
+static inline void edac_mc_handle_ce(struct mem_ctl_info *mci,
+ unsigned long page_frame_number,
+ unsigned long offset_in_page,
+ unsigned long syndrome, int row, int channel,
+ const char *msg)
+{
+ edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
+ page_frame_number, offset_in_page, syndrome,
+ row, channel, -1, msg, NULL, NULL);
+}
+
+static inline void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci,
+ const char *msg)
+{
+ edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
+ 0, 0, 0, -1, -1, -1, msg, NULL, NULL);
+}
+
+static inline void edac_mc_handle_ue(struct mem_ctl_info *mci,
+ unsigned long page_frame_number,
+ unsigned long offset_in_page, int row,
+ const char *msg)
+{
+ edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
+ page_frame_number, offset_in_page, 0,
+ row, -1, -1, msg, NULL, NULL);
+}
+
+static inline void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci,
+ const char *msg)
+{
+ edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
+ 0, 0, 0, -1, -1, -1, msg, NULL, NULL);
+}
+
+static inline void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci,
+ unsigned int csrow,
+ unsigned int channel0,
+ unsigned int channel1,
+ char *msg)
+{
+ /*
+ *FIXME: The error can also be at channel1 (e. g. at the second
+ * channel of the same branch). The fix is to push
+ * edac_mc_handle_error() call into each driver
+ */
+ edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
+ 0, 0, 0,
+ csrow, channel0, -1, msg, NULL, NULL);
+}
+
+static inline void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci,
+ unsigned int csrow,
+ unsigned int channel, char *msg)
+{
+ edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
+ 0, 0, 0,
+ csrow, channel, -1, msg, NULL, NULL);
+}
/*
* edac_device APIs
@@ -496,6 +554,7 @@ extern void edac_device_handle_ue(struct edac_device_ctl_info *edac_dev,
extern void edac_device_handle_ce(struct edac_device_ctl_info *edac_dev,
int inst_nr, int block_nr, const char *msg);
extern int edac_device_alloc_index(void);
+extern const char *edac_layer_name[];
/*
* edac_pci APIs
diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c
index 6ec967a..4365079 100644
--- a/drivers/edac/edac_mc.c
+++ b/drivers/edac/edac_mc.c
@@ -44,9 +44,25 @@ static void edac_mc_dump_channel(struct rank_info *chan)
debugf4("\tchannel = %p\n", chan);
debugf4("\tchannel->chan_idx = %d\n", chan->chan_idx);
debugf4("\tchannel->csrow = %p\n\n", chan->csrow);
- debugf4("\tdimm->ce_count = %d\n", chan->dimm->ce_count);
- debugf4("\tdimm->label = '%s'\n", chan->dimm->label);
- debugf4("\tdimm->nr_pages = 0x%x\n", chan->dimm->nr_pages);
+ debugf4("\tchannel->dimm = %p\n", chan->dimm);
+}
+
+static void edac_mc_dump_dimm(struct dimm_info *dimm)
+{
+ int i;
+
+ debugf4("\tdimm = %p\n", dimm);
+ debugf4("\tdimm->label = '%s'\n", dimm->label);
+ debugf4("\tdimm->nr_pages = 0x%x\n", dimm->nr_pages);
+ debugf4("\tdimm location ");
+ for (i = 0; i < dimm->mci->n_layers; i++) {
+ printk(KERN_CONT "%d", dimm->location[i]);
+ if (i < dimm->mci->n_layers - 1)
+ printk(KERN_CONT ".");
+ }
+ printk(KERN_CONT "\n");
+ debugf4("\tdimm->grain = %d\n", dimm->grain);
+ debugf4("\tdimm->nr_pages = 0x%x\n", dimm->nr_pages);
}
static void edac_mc_dump_csrow(struct csrow_info *csrow)
@@ -70,6 +86,8 @@ static void edac_mc_dump_mci(struct mem_ctl_info *mci)
debugf4("\tmci->edac_check = %p\n", mci->edac_check);
debugf3("\tmci->nr_csrows = %d, csrows = %p\n",
mci->nr_csrows, mci->csrows);
+ debugf3("\tmci->nr_dimms = %d, dimms = %p\n",
+ mci->tot_dimms, mci->dimms);
debugf3("\tdev = %p\n", mci->dev);
debugf3("\tmod_name:ctl_name = %s:%s\n", mci->mod_name, mci->ctl_name);
debugf3("\tpvt_info = %p\n\n", mci->pvt_info);
@@ -157,10 +175,12 @@ void *edac_align_ptr(void **p, unsigned size, int n_elems)
}
/**
- * edac_mc_alloc: Allocate a struct mem_ctl_info structure
- * @size_pvt: size of private storage needed
- * @nr_csrows: Number of CWROWS needed for this MC
- * @nr_chans: Number of channels for the MC
+ * edac_mc_alloc: Allocate and partially fill a struct mem_ctl_info structure
+ * @mc_num: Memory controller number
+ * @n_layers: Number of MC hierarchy layers
+ * layers: Describes each layer as seen by the Memory Controller
+ * @size_pvt: size of private storage needed
+ *
*
* Everything is kmalloc'ed as one big chunk - more efficient.
* Only can be used if all structures have the same lifetime - otherwise
@@ -168,22 +188,49 @@ void *edac_align_ptr(void **p, unsigned size, int n_elems)
*
* Use edac_mc_free() to free mc structures allocated by this function.
*
+ * NOTE: drivers handle multi-rank memories in different ways: in some
+ * drivers, one multi-rank memory stick is mapped as one entry, while, in
+ * others, a single multi-rank memory stick would be mapped into several
+ * entries. Currently, this function will allocate multiple struct dimm_info
+ * on such scenarios, as grouping the multiple ranks require drivers change.
+ *
* Returns:
* NULL allocation failed
* struct mem_ctl_info pointer
*/
-struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
- unsigned nr_chans, int edac_index)
+struct mem_ctl_info *new_edac_mc_alloc(unsigned mc_num,
+ unsigned n_layers,
+ struct edac_mc_layer *layers,
+ unsigned sz_pvt)
{
- void *ptr = NULL;
struct mem_ctl_info *mci;
- struct csrow_info *csi, *csrow;
+ struct edac_mc_layer *layer;
+ struct csrow_info *csi, *csr;
struct rank_info *chi, *chp, *chan;
struct dimm_info *dimm;
- void *pvt;
- unsigned size;
- int row, chn;
- int err;
+ u32 *ce_per_layer[EDAC_MAX_LAYERS], *ue_per_layer[EDAC_MAX_LAYERS];
+ unsigned pos[EDAC_MAX_LAYERS];
+ void *pvt, *ptr = NULL;
+ unsigned size, tot_dimms = 1, count = 1;
+ unsigned tot_csrows = 1, tot_channels = 1, tot_errcount = 0;
+ int i, j, err, row, chn;
+ bool per_rank = false;
+
+ BUG_ON(n_layers > EDAC_MAX_LAYERS || n_layers == 0);
+ /*
+ * Calculate the total amount of dimms and csrows/cschannels while
+ * in the old API emulation mode
+ */
+ for (i = 0; i < n_layers; i++) {
+ tot_dimms *= layers[i].size;
+ if (layers[i].is_virt_csrow)
+ tot_csrows *= layers[i].size;
+ else
+ tot_channels *= layers[i].size;
+
+ if (layers[i].type == EDAC_MC_LAYER_CHIP_SELECT)
+ per_rank = true;
+ }
/* Figure out the offsets of the various items from the start of an mc
* structure. We want the alignment of each item to be at least as
@@ -191,12 +238,27 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
* hardcode everything into a single struct.
*/
mci = edac_align_ptr(&ptr, sizeof(*mci), 1);
- csi = edac_align_ptr(&ptr, sizeof(*csi), nr_csrows);
- chi = edac_align_ptr(&ptr, sizeof(*chi), nr_csrows * nr_chans);
- dimm = edac_align_ptr(&ptr, sizeof(*dimm), nr_csrows * nr_chans);
+ layer = edac_align_ptr(&ptr, sizeof(*layer), n_layers);
+ csi = edac_align_ptr(&ptr, sizeof(*csi), tot_csrows);
+ chi = edac_align_ptr(&ptr, sizeof(*chi), tot_csrows * tot_channels);
+ dimm = edac_align_ptr(&ptr, sizeof(*dimm), tot_dimms);
+ for (i = 0; i < n_layers; i++) {
+ count *= layers[i].size;
+ debugf4("%s: errcount layer %d size %d\n", __func__, i, count);
+ ce_per_layer[i] = edac_align_ptr(&ptr, sizeof(u32), count);
+ ue_per_layer[i] = edac_align_ptr(&ptr, sizeof(u32), count);
+ tot_errcount += 2 * count;
+ }
+
+ debugf4("%s: allocating %d error counters\n", __func__, tot_errcount);
pvt = edac_align_ptr(&ptr, sz_pvt, 1);
size = ((unsigned long)pvt) + sz_pvt;
+ debugf1("%s(): allocating %u bytes for mci data (%d %s, %d csrows/channels)\n",
+ __func__, size,
+ tot_dimms,
+ per_rank ? "ranks" : "dimms",
+ tot_csrows * tot_channels);
mci = kzalloc(size, GFP_KERNEL);
if (mci == NULL)
return NULL;
@@ -204,42 +266,90 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
/* Adjust pointers so they point within the memory we just allocated
* rather than an imaginary chunk of memory located at address 0.
*/
+ layer = (struct edac_mc_layer *)(((char *)mci) + ((unsigned long)layer));
csi = (struct csrow_info *)(((char *)mci) + ((unsigned long)csi));
chi = (struct rank_info *)(((char *)mci) + ((unsigned long)chi));
dimm = (struct dimm_info *)(((char *)mci) + ((unsigned long)dimm));
+ for (i = 0; i < n_layers; i++) {
+ mci->ce_per_layer[i] = (u32 *)((char *)mci + ((unsigned long)ce_per_layer[i]));
+ mci->ue_per_layer[i] = (u32 *)((char *)mci + ((unsigned long)ue_per_layer[i]));
+ }
pvt = sz_pvt ? (((char *)mci) + ((unsigned long)pvt)) : NULL;
/* setup index and various internal pointers */
- mci->mc_idx = edac_index;
+ mci->mc_idx = mc_num;
mci->csrows = csi;
mci->dimms = dimm;
+ mci->tot_dimms = tot_dimms;
mci->pvt_info = pvt;
- mci->nr_csrows = nr_csrows;
+ mci->n_layers = n_layers;
+ mci->layers = layer;
+ memcpy(mci->layers, layers, sizeof(*layer) * n_layers);
+ mci->nr_csrows = tot_csrows;
+ mci->num_cschannel = tot_channels;
+ mci->mem_is_per_rank = per_rank;
/*
- * For now, assumes that a per-csrow arrangement for dimms.
- * This will be latter changed.
+ * Fill the csrow struct
*/
- dimm = mci->dimms;
-
- for (row = 0; row < nr_csrows; row++) {
- csrow = &csi[row];
- csrow->csrow_idx = row;
- csrow->mci = mci;
- csrow->nr_channels = nr_chans;
- chp = &chi[row * nr_chans];
- csrow->channels = chp;
-
- for (chn = 0; chn < nr_chans; chn++) {
+ for (row = 0; row < tot_csrows; row++) {
+ csr = &csi[row];
+ csr->csrow_idx = row;
+ csr->mci = mci;
+ csr->nr_channels = tot_channels;
+ chp = &chi[row * tot_channels];
+ csr->channels = chp;
+
+ for (chn = 0; chn < tot_channels; chn++) {
chan = &chp[chn];
chan->chan_idx = chn;
- chan->csrow = csrow;
+ chan->csrow = csr;
+ }
+ }
- mci->csrows[row].channels[chn].dimm = dimm;
- dimm->csrow = row;
- dimm->csrow_channel = chn;
- dimm++;
- mci->nr_dimms++;
+ /*
+ * Fill the dimm struct
+ */
+ memset(&pos, 0, sizeof(pos));
+ row = 0;
+ chn = 0;
+ debugf4("%s: initializing %d %s\n", __func__, tot_dimms,
+ per_rank ? "ranks" : "dimms");
+ for (i = 0; i < tot_dimms; i++) {
+ chan = &csi[row].channels[chn];
+ dimm = EDAC_DIMM_PTR(layer, mci->dimms, n_layers,
+ pos[0], pos[1], pos[2]);
+ dimm->mci = mci;
+
+ debugf2("%s: %d: %s%zd (%d:%d:%d): row %d, chan %d\n", __func__,
+ i, per_rank ? "rank" : "dimm", (dimm - mci->dimms),
+ pos[0], pos[1], pos[2], row, chn);
+
+ /* Copy DIMM location */
+ for (j = 0; j < n_layers; j++)
+ dimm->location[j] = pos[j];
+
+ /* Link it to the csrows old API data */
+ chan->dimm = dimm;
+ dimm->csrow = row;
+ dimm->cschannel = chn;
+
+ /* Increment csrow location */
+ for (j = n_layers - 1; j >= 0; j--)
+ if (layers[j].is_virt_csrow)
+ break;
+ row++;
+ if (row == tot_csrows) {
+ row = 0;
+ chn++;
+ }
+
+ /* Increment dimm location */
+ for (j = n_layers - 1; j >= 0; j--) {
+ pos[j]++;
+ if (pos[j] < layers[j].size)
+ break;
+ pos[j] = 0;
}
}
@@ -263,6 +373,46 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
*/
return mci;
}
+EXPORT_SYMBOL_GPL(new_edac_mc_alloc);
+
+/**
+ * edac_mc_alloc: Allocate and partially fill a struct mem_ctl_info structure
+ * @mc_num: Memory controller number
+ * @n_layers: Number of layers at the MC hierarchy
+ * layers: Describes each layer as seen by the Memory Controller
+ * @size_pvt: Size of private storage needed
+ *
+ *
+ * FIXME: drivers handle multi-rank memories in different ways: some
+ * drivers map multi-ranked DIMMs as one DIMM while others
+ * as several DIMMs.
+ *
+ * Everything is kmalloc'ed as one big chunk - more efficient.
+ * It can only be used if all structures have the same lifetime - otherwise
+ * you have to allocate and initialize your own structures.
+ *
+ * Use edac_mc_free() to free mc structures allocated by this function.
+ *
+ * Returns:
+ * On failure: NULL
+ * On success: struct mem_ctl_info pointer
+ */
+
+struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
+ unsigned nr_chans, int mc_num)
+{
+ unsigned n_layers = 2;
+ struct edac_mc_layer layers[n_layers];
+
+ layers[0].type = EDAC_MC_LAYER_CHIP_SELECT;
+ layers[0].size = nr_csrows;
+ layers[0].is_virt_csrow = true;
+ layers[1].type = EDAC_MC_LAYER_CHANNEL;
+ layers[1].size = nr_chans;
+ layers[1].is_virt_csrow = false;
+
+ return new_edac_mc_alloc(mc_num, ARRAY_SIZE(layers), layers, sz_pvt);
+}
EXPORT_SYMBOL_GPL(edac_mc_alloc);
/**
@@ -528,7 +678,6 @@ EXPORT_SYMBOL(edac_mc_find);
* edac_mc_add_mc: Insert the 'mci' structure into the mci global list and
* create sysfs entries associated with mci structure
* @mci: pointer to the mci structure to be added to the list
- * @mc_idx: A unique numeric identifier to be assigned to the 'mci' structure.
*
* Return:
* 0 Success
@@ -555,6 +704,8 @@ int edac_mc_add_mc(struct mem_ctl_info *mci)
edac_mc_dump_channel(&mci->csrows[i].
channels[j]);
}
+ for (i = 0; i < mci->tot_dimms; i++)
+ edac_mc_dump_dimm(&mci->dimms[i]);
}
#endif
mutex_lock(&mem_ctls_mutex);
@@ -712,261 +863,289 @@ int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci, unsigned long page)
}
EXPORT_SYMBOL_GPL(edac_mc_find_csrow_by_page);
-/* FIXME - setable log (warning/emerg) levels */
-/* FIXME - integrate with evlog: http://evlog.sourceforge.net/ */
-void edac_mc_handle_ce(struct mem_ctl_info *mci,
- unsigned long page_frame_number,
- unsigned long offset_in_page, unsigned long syndrome,
- int row, int channel, const char *msg)
+const char *edac_layer_name[] = {
+ [EDAC_MC_LAYER_BRANCH] = "branch",
+ [EDAC_MC_LAYER_CHANNEL] = "channel",
+ [EDAC_MC_LAYER_SLOT] = "slot",
+ [EDAC_MC_LAYER_CHIP_SELECT] = "csrow",
+};
+EXPORT_SYMBOL_GPL(edac_layer_name);
+
+static void edac_inc_ce_error(struct mem_ctl_info *mci,
+ bool enable_per_layer_report,
+ const int pos[EDAC_MAX_LAYERS])
{
- unsigned long remapped_page;
- char *label = NULL;
- u32 grain;
+ int i, index = 0;
- debugf3("MC%d: %s()\n", mci->mc_idx, __func__);
+ mci->ce_count++;
- /* FIXME - maybe make panic on INTERNAL ERROR an option */
- if (row >= mci->nr_csrows || row < 0) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: row out of range "
- "(%d >= %d)\n", row, mci->nr_csrows);
- edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR");
+ if (!enable_per_layer_report) {
+ mci->ce_noinfo_count++;
return;
}
- if (channel >= mci->csrows[row].nr_channels || channel < 0) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: channel out of range "
- "(%d >= %d)\n", channel,
- mci->csrows[row].nr_channels);
- edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR");
+ for (i = 0; i < mci->n_layers; i++) {
+ if (pos[i] < 0)
+ break;
+ index += pos[i];
+ mci->ce_per_layer[i][index]++;
+
+ if (i < mci->n_layers - 1)
+ index *= mci->layers[i + 1].size;
+ }
+}
+
+static void edac_inc_ue_error(struct mem_ctl_info *mci,
+ bool enable_per_layer_report,
+ const int pos[EDAC_MAX_LAYERS])
+{
+ int i, index = 0;
+
+ mci->ue_count++;
+
+ if (!enable_per_layer_report) {
+ mci->ce_noinfo_count++;
return;
}
- label = mci->csrows[row].channels[channel].dimm->label;
- grain = mci->csrows[row].channels[channel].dimm->grain;
+ for (i = 0; i < mci->n_layers; i++) {
+ if (pos[i] < 0)
+ break;
+ index += pos[i];
+ mci->ue_per_layer[i][index]++;
+
+ if (i < mci->n_layers - 1)
+ index *= mci->layers[i + 1].size;
+ }
+}
+
+static void edac_ce_error(struct mem_ctl_info *mci,
+ const int pos[EDAC_MAX_LAYERS],
+ const char *msg,
+ const char *location,
+ const char *label,
+ const char *detail,
+ const char *other_detail,
+ const bool enable_per_layer_report,
+ const unsigned long page_frame_number,
+ const unsigned long offset_in_page,
+ u32 grain)
+{
+ unsigned long remapped_page;
if (edac_mc_get_log_ce())
- /* FIXME - put in DIMM location */
edac_mc_printk(mci, KERN_WARNING,
- "CE page 0x%lx, offset 0x%lx, grain %d, syndrome "
- "0x%lx, row %d, channel %d, label \"%s\": %s\n",
- page_frame_number, offset_in_page,
- grain, syndrome, row, channel,
- label, msg);
-
- mci->ce_count++;
- mci->csrows[row].ce_count++;
- mci->csrows[row].channels[channel].dimm->ce_count++;
- mci->csrows[row].channels[channel].ce_count++;
+ "CE %s on %s (%s%s %s)\n",
+ msg, label, location,
+ detail, other_detail);
+ edac_inc_ce_error(mci, enable_per_layer_report, pos);
if (mci->scrub_mode & SCRUB_SW_SRC) {
/*
- * Some MC's can remap memory so that it is still available
- * at a different address when PCI devices map into memory.
- * MC's that can't do this lose the memory where PCI devices
- * are mapped. This mapping is MC dependent and so we call
- * back into the MC driver for it to map the MC page to
- * a physical (CPU) page which can then be mapped to a virtual
- * page - which can then be scrubbed.
- */
+ * Some memory controllers (called MCs below) can remap
+ * memory so that it is still available at a different
+ * address when PCI devices map into memory.
+ * MC's that can't do this, lose the memory where PCI
+ * devices are mapped. This mapping is MC-dependent
+ * and so we call back into the MC driver for it to
+ * map the MC page to a physical (CPU) page which can
+ * then be mapped to a virtual page - which can then
+ * be scrubbed.
+ */
remapped_page = mci->ctl_page_to_phys ?
mci->ctl_page_to_phys(mci, page_frame_number) :
page_frame_number;
- edac_mc_scrub_block(remapped_page, offset_in_page, grain);
+ edac_mc_scrub_block(remapped_page,
+ offset_in_page, grain);
}
}
-EXPORT_SYMBOL_GPL(edac_mc_handle_ce);
-void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci, const char *msg)
+static void edac_ue_error(struct mem_ctl_info *mci,
+ const int pos[EDAC_MAX_LAYERS],
+ const char *msg,
+ const char *location,
+ const char *label,
+ const char *detail,
+ const char *other_detail,
+ const bool enable_per_layer_report)
{
- if (edac_mc_get_log_ce())
+ if (edac_mc_get_log_ue())
edac_mc_printk(mci, KERN_WARNING,
- "CE - no information available: %s\n", msg);
+ "UE %s on %s (%s%s %s)\n",
+ msg, label, location, detail, other_detail);
- mci->ce_noinfo_count++;
- mci->ce_count++;
+ if (edac_mc_get_panic_on_ue())
+ panic("UE %s on %s (%s%s %s)\n",
+ msg, label, location, detail, other_detail);
+
+ edac_inc_ue_error(mci, enable_per_layer_report, pos);
}
-EXPORT_SYMBOL_GPL(edac_mc_handle_ce_no_info);
-void edac_mc_handle_ue(struct mem_ctl_info *mci,
- unsigned long page_frame_number,
- unsigned long offset_in_page, int row, const char *msg)
+#define OTHER_LABEL " or "
+void edac_mc_handle_error(const enum hw_event_mc_err_type type,
+ struct mem_ctl_info *mci,
+ const unsigned long page_frame_number,
+ const unsigned long offset_in_page,
+ const unsigned long syndrome,
+ const int layer0,
+ const int layer1,
+ const int layer2,
+ const char *msg,
+ const char *other_detail,
+ const void *mcelog)
{
- int len = EDAC_MC_LABEL_LEN * 4;
- char labels[len + 1];
- char *pos = labels;
- int chan;
- int chars;
- char *label = NULL;
+ /* FIXME: too much for stack: move it to some pre-alocated area */
+ char detail[80], location[80];
+ char label[(EDAC_MC_LABEL_LEN + 1 + sizeof(OTHER_LABEL)) * mci->tot_dimms];
+ char *p;
+ int row = -1, chan = -1;
+ int pos[EDAC_MAX_LAYERS] = { layer0, layer1, layer2 };
+ int i;
u32 grain;
+ bool enable_per_layer_report = false;
debugf3("MC%d: %s()\n", mci->mc_idx, __func__);
- /* FIXME - maybe make panic on INTERNAL ERROR an option */
- if (row >= mci->nr_csrows || row < 0) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: row out of range "
- "(%d >= %d)\n", row, mci->nr_csrows);
- edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR");
- return;
- }
-
- grain = mci->csrows[row].channels[0].dimm->grain;
- label = mci->csrows[row].channels[0].dimm->label;
- chars = snprintf(pos, len + 1, "%s", label);
- len -= chars;
- pos += chars;
-
- for (chan = 1; (chan < mci->csrows[row].nr_channels) && (len > 0);
- chan++) {
- label = mci->csrows[row].channels[chan].dimm->label;
- chars = snprintf(pos, len + 1, ":%s", label);
- len -= chars;
- pos += chars;
+ /*
+ * Check if the event report is consistent and if the memory
+ * location is known. If it is known, enable_per_layer_report will be
+ * true, the DIMM(s) label info will be filled and the per-layer
+ * error counters will be incremented.
+ */
+ for (i = 0; i < mci->n_layers; i++) {
+ if (pos[i] >= (int)mci->layers[i].size) {
+ if (type == HW_EVENT_ERR_CORRECTED)
+ p = "CE";
+ else
+ p = "UE";
+
+ edac_mc_printk(mci, KERN_ERR,
+ "INTERNAL ERROR: %s value is out of range (%d >= %d)\n",
+ edac_layer_name[mci->layers[i].type],
+ pos[i], mci->layers[i].size);
+ /*
+ * Instead of just returning it, let's use what's
+ * known about the error. The increment routines and
+ * the DIMM filter logic will do the right thing by
+ * pointing the likely damaged DIMMs.
+ */
+ pos[i] = -1;
+ }
+ if (pos[i] >= 0)
+ enable_per_layer_report = true;
}
- if (edac_mc_get_log_ue())
- edac_mc_printk(mci, KERN_EMERG,
- "UE page 0x%lx, offset 0x%lx, grain %d, row %d, "
- "labels \"%s\": %s\n", page_frame_number,
- offset_in_page, grain, row, labels, msg);
-
- if (edac_mc_get_panic_on_ue())
- panic("EDAC MC%d: UE page 0x%lx, offset 0x%lx, grain %d, "
- "row %d, labels \"%s\": %s\n", mci->mc_idx,
- page_frame_number, offset_in_page,
- grain, row, labels, msg);
-
- mci->ue_count++;
- mci->csrows[row].ue_count++;
-}
-EXPORT_SYMBOL_GPL(edac_mc_handle_ue);
+ /*
+ * Get the dimm label/grain that applies to the match criteria.
+ * As the error algorithm may not be able to point to just one memory
+ * stick, the logic here will get all possible labels that could
+ * pottentially be affected by the error.
+ * On FB-DIMM memory controllers, for uncorrected errors, it is common
+ * to have only the MC channel and the MC dimm (also called "branch")
+ * but the channel is not known, as the memory is arranged in pairs,
+ * where each memory belongs to a separate channel within the same
+ * branch.
+ */
+ grain = 0;
+ p = label;
+ *p = '\0';
+ for (i = 0; i < mci->tot_dimms; i++) {
+ struct dimm_info *dimm = &mci->dimms[i];
-void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci, const char *msg)
-{
- if (edac_mc_get_panic_on_ue())
- panic("EDAC MC%d: Uncorrected Error", mci->mc_idx);
+ if (layer0 >= 0 && layer0 != dimm->location[0])
+ continue;
+ if (layer1 >= 0 && layer1 != dimm->location[1])
+ continue;
+ if (layer2 >= 0 && layer2 != dimm->location[2])
+ continue;
- if (edac_mc_get_log_ue())
- edac_mc_printk(mci, KERN_WARNING,
- "UE - no information available: %s\n", msg);
- mci->ue_noinfo_count++;
- mci->ue_count++;
-}
-EXPORT_SYMBOL_GPL(edac_mc_handle_ue_no_info);
+ /* get the max grain, over the error match range */
+ if (dimm->grain > grain)
+ grain = dimm->grain;
-/*************************************************************
- * On Fully Buffered DIMM modules, this help function is
- * called to process UE events
- */
-void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci,
- unsigned int csrow,
- unsigned int channela,
- unsigned int channelb, char *msg)
-{
- int len = EDAC_MC_LABEL_LEN * 4;
- char labels[len + 1];
- char *pos = labels;
- int chars;
- char *label;
-
- if (csrow >= mci->nr_csrows) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: row out of range (%d >= %d)\n",
- csrow, mci->nr_csrows);
- edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR");
- return;
- }
-
- if (channela >= mci->csrows[csrow].nr_channels) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: channel-a out of range "
- "(%d >= %d)\n",
- channela, mci->csrows[csrow].nr_channels);
- edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR");
- return;
+ /*
+ * If the error is memory-controller wide, there's no need to
+ * seek for the affected DIMMs because the whole
+ * channel/memory controller/... may be affected.
+ * Also, don't show errors for empty DIMM slots.
+ */
+ if (enable_per_layer_report && dimm->nr_pages) {
+ if (p != label) {
+ strcpy(p, OTHER_LABEL);
+ p += strlen(OTHER_LABEL);
+ }
+ strcpy(p, dimm->label);
+ p += strlen(p);
+ *p = '\0';
+
+ /*
+ * get csrow/channel of the DIMM, in order to allow
+ * incrementing the compat API counters
+ */
+ debugf4("%s: %s csrows map: (%d,%d)\n",
+ __func__,
+ mci->mem_is_per_rank ? "rank" : "dimm",
+ dimm->csrow, dimm->cschannel);
+
+ if (row == -1)
+ row = dimm->csrow;
+ else if (row >= 0 && row != dimm->csrow)
+ row = -2;
+
+ if (chan == -1)
+ chan = dimm->cschannel;
+ else if (chan >= 0 && chan != dimm->cschannel)
+ chan = -2;
+ }
}
- if (channelb >= mci->csrows[csrow].nr_channels) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: channel-b out of range "
- "(%d >= %d)\n",
- channelb, mci->csrows[csrow].nr_channels);
- edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR");
- return;
+ if (!enable_per_layer_report) {
+ strcpy(label, "any memory");
+ } else {
+ debugf4("%s: csrow/channel to increment: (%d,%d)\n",
+ __func__, row, chan);
+ if (p == label)
+ strcpy(label, "unknown memory");
+ if (type == HW_EVENT_ERR_CORRECTED) {
+ if (row >= 0) {
+ mci->csrows[row].ce_count++;
+ if (chan >= 0)
+ mci->csrows[row].channels[chan].ce_count++;
+ }
+ } else
+ if (row >= 0)
+ mci->csrows[row].ue_count++;
}
- mci->ue_count++;
- mci->csrows[csrow].ue_count++;
-
- /* Generate the DIMM labels from the specified channels */
- label = mci->csrows[csrow].channels[channela].dimm->label;
- chars = snprintf(pos, len + 1, "%s", label);
- len -= chars;
- pos += chars;
-
- chars = snprintf(pos, len + 1, "-%s",
- mci->csrows[csrow].channels[channelb].dimm->label);
-
- if (edac_mc_get_log_ue())
- edac_mc_printk(mci, KERN_EMERG,
- "UE row %d, channel-a= %d channel-b= %d "
- "labels \"%s\": %s\n", csrow, channela, channelb,
- labels, msg);
-
- if (edac_mc_get_panic_on_ue())
- panic("UE row %d, channel-a= %d channel-b= %d "
- "labels \"%s\": %s\n", csrow, channela,
- channelb, labels, msg);
-}
-EXPORT_SYMBOL(edac_mc_handle_fbd_ue);
-
-/*************************************************************
- * On Fully Buffered DIMM modules, this help function is
- * called to process CE events
- */
-void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci,
- unsigned int csrow, unsigned int channel, char *msg)
-{
- char *label = NULL;
+ /* Fill the RAM location data */
+ p = location;
+ for (i = 0; i < mci->n_layers; i++) {
+ if (pos[i] < 0)
+ continue;
- /* Ensure boundary values */
- if (csrow >= mci->nr_csrows) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: row out of range (%d >= %d)\n",
- csrow, mci->nr_csrows);
- edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR");
- return;
- }
- if (channel >= mci->csrows[csrow].nr_channels) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: channel out of range (%d >= %d)\n",
- channel, mci->csrows[csrow].nr_channels);
- edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR");
- return;
+ p += sprintf(p, "%s %d ",
+ edac_layer_name[mci->layers[i].type],
+ pos[i]);
}
- label = mci->csrows[csrow].channels[channel].dimm->label;
+ /* Memory type dependent details about the error */
+ if (type == HW_EVENT_ERR_CORRECTED) {
+ snprintf(detail, sizeof(detail),
+ "page 0x%lx offset 0x%lx grain %d syndrome 0x%lx",
+ page_frame_number, offset_in_page,
+ grain, syndrome);
- if (edac_mc_get_log_ce())
- /* FIXME - put in DIMM location */
- edac_mc_printk(mci, KERN_WARNING,
- "CE row %d, channel %d, label \"%s\": %s\n",
- csrow, channel, label, msg);
+ edac_ce_error(mci, pos, msg, location, label, detail,
+ other_detail, enable_per_layer_report,
+ page_frame_number, offset_in_page, grain);
+ } else {
+ snprintf(detail, sizeof(detail),
+ "page 0x%lx offset 0x%lx grain %d",
+ page_frame_number, offset_in_page, grain);
- mci->ce_count++;
- mci->csrows[csrow].ce_count++;
- mci->csrows[csrow].channels[channel].dimm->ce_count++;
- mci->csrows[csrow].channels[channel].ce_count++;
+ edac_ue_error(mci, pos, msg, location, label, detail,
+ other_detail, enable_per_layer_report);
+ }
}
-EXPORT_SYMBOL(edac_mc_handle_fbd_ce);
+EXPORT_SYMBOL_GPL(edac_mc_handle_error);
diff --git a/include/linux/edac.h b/include/linux/edac.h
index 3b8798d..c8f507d 100644
--- a/include/linux/edac.h
+++ b/include/linux/edac.h
@@ -412,18 +412,20 @@ struct edac_mc_layer {
/* FIXME: add the proper per-location error counts */
struct dimm_info {
char label[EDAC_MC_LABEL_LEN + 1]; /* DIMM label on motherboard */
- unsigned memory_controller;
- unsigned csrow;
- unsigned csrow_channel;
+
+ /* Memory location data */
+ unsigned location[EDAC_MAX_LAYERS];
+
+ struct mem_ctl_info *mci; /* the parent */
u32 grain; /* granularity of reported error in bytes */
enum dev_type dtype; /* memory device type */
enum mem_type mtype; /* memory dimm type */
enum edac_type edac_mode; /* EDAC mode for this dimm */
- u32 nr_pages; /* number of pages in csrow */
+ u32 nr_pages; /* number of pages on this dimm */
- u32 ce_count; /* Correctable Errors for this dimm */
+ unsigned csrow, cschannel; /* Points to the old API data */
};
/**
@@ -443,9 +445,10 @@ struct dimm_info {
*/
struct rank_info {
int chan_idx;
- u32 ce_count;
struct csrow_info *csrow;
struct dimm_info *dimm;
+
+ u32 ce_count; /* Correctable Errors for this csrow */
};
struct csrow_info {
@@ -541,13 +544,18 @@ struct mem_ctl_info {
unsigned long (*ctl_page_to_phys) (struct mem_ctl_info * mci,
unsigned long page);
int mc_idx;
- int nr_csrows;
struct csrow_info *csrows;
+ unsigned nr_csrows, num_cschannel;
+
+ /* Memory Controller hierarchy */
+ unsigned n_layers;
+ struct edac_mc_layer *layers;
+ bool mem_is_per_rank;
/*
* DIMM info. Will eventually remove the entire csrows_info some day
*/
- unsigned nr_dimms;
+ unsigned tot_dimms;
struct dimm_info *dimms;
/*
@@ -562,12 +570,16 @@ struct mem_ctl_info {
const char *dev_name;
char proc_name[MC_PROC_NAME_MAX_LEN + 1];
void *pvt_info;
- u32 ue_noinfo_count; /* Uncorrectable Errors w/o info */
- u32 ce_noinfo_count; /* Correctable Errors w/o info */
- u32 ue_count; /* Total Uncorrectable Errors for this MC */
- u32 ce_count; /* Total Correctable Errors for this MC */
unsigned long start_time; /* mci load start time (in jiffies) */
+ /*
+ * drivers shouldn't access those fields directly, as the core
+ * already handles that.
+ */
+ u32 ce_noinfo_count, ue_noinfo_count;
+ u32 ue_count, ce_count;
+ u32 *ce_per_layer[EDAC_MAX_LAYERS], *ue_per_layer[EDAC_MAX_LAYERS];
+
struct completion complete;
/* edac sysfs device control */
@@ -580,7 +592,7 @@ struct mem_ctl_info {
* by the low level driver.
*
* Set by the low level driver to provide attributes at the
- * controller level, same level as 'ue_count' and 'ce_count' above.
+ * controller level.
* An array of structures, NULL terminated
*
* If attributes are desired, then set to array of attributes
^ permalink raw reply related
* Re: [PATCH EDACv16 1/2] edac: Change internal representation to work with layers, second version
From: Borislav Petkov @ 2012-05-04 10:16 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Shaohui Xie, Jason Uhlenkott, Aristeu Rozanski, Hitoshi Mitake,
Mark Gross, Dmitry Eremin-Solenikov, Ranganathan Desikan,
Borislav Petkov, Egor Martovetsky, Niklas Söderlund,
Tim Small, Arvind R., Chris Metcalf, Olof Johansson,
Doug Thompson, Linux Edac Mailing List, Michal Marek, Jiri Kosina,
Linux Kernel Mailing List, Joe Perches, Andrew Morton,
linuxppc-dev
In-Reply-To: <4FA29356.3040601@redhat.com>
On Thu, May 03, 2012 at 11:16:54AM -0300, Mauro Carvalho Chehab wrote:
> edac: Change internal representation to work with layers
>
> From: Mauro Carvalho Chehab <mchehab@redhat.com>
>
> Change the EDAC internal representation to work with non-csrow
> based memory controllers.
>
> There are lots of those memory controllers nowadays, and more
> are coming. So, the EDAC internal representation needs to be
> changed, in order to work with those memory controllers, while
> preserving backward compatibility with the old ones.
>
> The edac core was written with the idea that memory controllers
> are able to directly access csrows.
>
> This is not true for FB-DIMM and RAMBUS memory controllers.
>
> Also, some recent advanced memory controllers don't present a per-csrows
> view. Instead, they view memories as DIMMs, instead of ranks.
>
> So, change the allocation and error report routines to allow
> them to work with all types of architectures.
>
> This will allow the removal of several hacks with FB-DIMM and RAMBUS
> memory controllers.
>
> Also, several tests were done on different platforms using different
> x86 drivers.
>
> TODO: a multi-rank DIMMs are currently represented by multiple DIMM
> entries in struct dimm_info. That means that changing a label for one
> rank won't change the same label for the other ranks at the same DIMM.
> This bug is present since the beginning of the EDAC, so it is not a big
> deal. However, on several drivers, it is possible to fix this issue, but
> it should be a per-driver fix, as the csrow => DIMM arrangement may not
> be equal for all. So, don't try to fix it here yet.
>
> I tried to make this patch as short as possible, preceding it with
> several other patches that simplified the logic here. Yet, as the
> internal API changes, all drivers need changes. The changes are
> generally bigger in the drivers for FB-DIMMs.
>
> Cc: Aristeu Rozanski <arozansk@redhat.com>
> Cc: Doug Thompson <norsk5@yahoo.com>
> Cc: Borislav Petkov <borislav.petkov@amd.com>
> Cc: Mark Gross <mark.gross@intel.com>
> Cc: Jason Uhlenkott <juhlenko@akamai.com>
> Cc: Tim Small <tim@buttersideup.com>
> Cc: Ranganathan Desikan <ravi@jetztechnologies.com>
> Cc: "Arvind R." <arvino55@gmail.com>
> Cc: Olof Johansson <olof@lixom.net>
> Cc: Egor Martovetsky <egor@pasemi.com>
> Cc: Chris Metcalf <cmetcalf@tilera.com>
> Cc: Michal Marek <mmarek@suse.cz>
> Cc: Jiri Kosina <jkosina@suse.cz>
> Cc: Joe Perches <joe@perches.com>
> Cc: Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
> Cc: Hitoshi Mitake <h.mitake@gmail.com>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: "Niklas Söderlund" <niklas.soderlund@ericsson.com>
> Cc: Shaohui Xie <Shaohui.Xie@freescale.com>
> Cc: Josh Boyer <jwboyer@gmail.com>
> Cc: linuxppc-dev@lists.ozlabs.org
> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
>
> ---
> v18: Addresses the pointed issues on v17 review
>
> diff --git a/drivers/edac/edac_core.h b/drivers/edac/edac_core.h
> index e48ab31..1286c5e 100644
> --- a/drivers/edac/edac_core.h
> +++ b/drivers/edac/edac_core.h
> @@ -447,8 +447,12 @@ static inline void pci_write_bits32(struct pci_dev *pdev, int offset,
>
> #endif /* CONFIG_PCI */
>
> -extern struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
> - unsigned nr_chans, int edac_index);
> +struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
> + unsigned nr_chans, int edac_index);
> +struct mem_ctl_info *new_edac_mc_alloc(unsigned edac_index,
> + unsigned n_layers,
> + struct edac_mc_layer *layers,
> + unsigned sz_pvt);
> extern int edac_mc_add_mc(struct mem_ctl_info *mci);
> extern void edac_mc_free(struct mem_ctl_info *mci);
> extern struct mem_ctl_info *edac_mc_find(int idx);
> @@ -467,24 +471,78 @@ extern int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci,
> * reporting logic and function interface - reduces conditional
> * statement clutter and extra function arguments.
> */
> -extern void edac_mc_handle_ce(struct mem_ctl_info *mci,
> - unsigned long page_frame_number,
> - unsigned long offset_in_page,
> - unsigned long syndrome, int row, int channel,
> - const char *msg);
> -extern void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci,
> - const char *msg);
> -extern void edac_mc_handle_ue(struct mem_ctl_info *mci,
> - unsigned long page_frame_number,
> - unsigned long offset_in_page, int row,
> - const char *msg);
> -extern void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci,
> - const char *msg);
> -extern void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci, unsigned int csrow,
> - unsigned int channel0, unsigned int channel1,
> - char *msg);
> -extern void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci, unsigned int csrow,
> - unsigned int channel, char *msg);
> +
> +void edac_mc_handle_error(const enum hw_event_mc_err_type type,
> + struct mem_ctl_info *mci,
> + const unsigned long page_frame_number,
> + const unsigned long offset_in_page,
> + const unsigned long syndrome,
> + const int layer0,
> + const int layer1,
> + const int layer2,
> + const char *msg,
> + const char *other_detail,
> + const void *mcelog);
> +
> +static inline void edac_mc_handle_ce(struct mem_ctl_info *mci,
> + unsigned long page_frame_number,
> + unsigned long offset_in_page,
> + unsigned long syndrome, int row, int channel,
> + const char *msg)
> +{
> + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
> + page_frame_number, offset_in_page, syndrome,
> + row, channel, -1, msg, NULL, NULL);
> +}
> +
> +static inline void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci,
> + const char *msg)
> +{
> + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
> + 0, 0, 0, -1, -1, -1, msg, NULL, NULL);
> +}
> +
> +static inline void edac_mc_handle_ue(struct mem_ctl_info *mci,
> + unsigned long page_frame_number,
> + unsigned long offset_in_page, int row,
> + const char *msg)
> +{
> + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
> + page_frame_number, offset_in_page, 0,
> + row, -1, -1, msg, NULL, NULL);
> +}
> +
> +static inline void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci,
> + const char *msg)
> +{
> + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
> + 0, 0, 0, -1, -1, -1, msg, NULL, NULL);
> +}
> +
> +static inline void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci,
> + unsigned int csrow,
> + unsigned int channel0,
> + unsigned int channel1,
> + char *msg)
> +{
> + /*
> + *FIXME: The error can also be at channel1 (e. g. at the second
> + * channel of the same branch). The fix is to push
> + * edac_mc_handle_error() call into each driver
> + */
> + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
> + 0, 0, 0,
> + csrow, channel0, -1, msg, NULL, NULL);
> +}
> +
> +static inline void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci,
> + unsigned int csrow,
> + unsigned int channel, char *msg)
> +{
> + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
> + 0, 0, 0,
> + csrow, channel, -1, msg, NULL, NULL);
> +}
>
> /*
> * edac_device APIs
> @@ -496,6 +554,7 @@ extern void edac_device_handle_ue(struct edac_device_ctl_info *edac_dev,
> extern void edac_device_handle_ce(struct edac_device_ctl_info *edac_dev,
> int inst_nr, int block_nr, const char *msg);
> extern int edac_device_alloc_index(void);
> +extern const char *edac_layer_name[];
>
> /*
> * edac_pci APIs
> diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c
> index 6ec967a..10cebb8 100644
> --- a/drivers/edac/edac_mc.c
> +++ b/drivers/edac/edac_mc.c
> @@ -44,9 +44,25 @@ static void edac_mc_dump_channel(struct rank_info *chan)
> debugf4("\tchannel = %p\n", chan);
> debugf4("\tchannel->chan_idx = %d\n", chan->chan_idx);
> debugf4("\tchannel->csrow = %p\n\n", chan->csrow);
> - debugf4("\tdimm->ce_count = %d\n", chan->dimm->ce_count);
> - debugf4("\tdimm->label = '%s'\n", chan->dimm->label);
> - debugf4("\tdimm->nr_pages = 0x%x\n", chan->dimm->nr_pages);
> + debugf4("\tchannel->dimm = %p\n", chan->dimm);
> +}
> +
> +static void edac_mc_dump_dimm(struct dimm_info *dimm)
> +{
> + int i;
> +
> + debugf4("\tdimm = %p\n", dimm);
> + debugf4("\tdimm->label = '%s'\n", dimm->label);
> + debugf4("\tdimm->nr_pages = 0x%x\n", dimm->nr_pages);
> + debugf4("\tdimm location ");
> + for (i = 0; i < dimm->mci->n_layers; i++) {
> + printk(KERN_CONT "%d", dimm->location[i]);
> + if (i < dimm->mci->n_layers - 1)
> + printk(KERN_CONT ".");
> + }
> + printk(KERN_CONT "\n");
> + debugf4("\tdimm->grain = %d\n", dimm->grain);
> + debugf4("\tdimm->nr_pages = 0x%x\n", dimm->nr_pages);
> }
>
> static void edac_mc_dump_csrow(struct csrow_info *csrow)
> @@ -70,6 +86,8 @@ static void edac_mc_dump_mci(struct mem_ctl_info *mci)
> debugf4("\tmci->edac_check = %p\n", mci->edac_check);
> debugf3("\tmci->nr_csrows = %d, csrows = %p\n",
> mci->nr_csrows, mci->csrows);
> + debugf3("\tmci->nr_dimms = %d, dimms = %p\n",
> + mci->tot_dimms, mci->dimms);
> debugf3("\tdev = %p\n", mci->dev);
> debugf3("\tmod_name:ctl_name = %s:%s\n", mci->mod_name, mci->ctl_name);
> debugf3("\tpvt_info = %p\n\n", mci->pvt_info);
> @@ -157,10 +175,20 @@ void *edac_align_ptr(void **p, unsigned size, int n_elems)
> }
>
> /**
> - * edac_mc_alloc: Allocate a struct mem_ctl_info structure
> - * @size_pvt: size of private storage needed
> - * @nr_csrows: Number of CWROWS needed for this MC
> - * @nr_chans: Number of channels for the MC
> + * edac_mc_alloc: Allocate and partially fill a struct mem_ctl_info structure
> + * @mc_num: Memory controller number
> + * @n_layers: Number of MC hierarchy layers
> + * layers: Describes each layer as seen by the Memory Controller
> + * @size_pvt: size of private storage needed
> + *
> + *
> + * Non-csrow based drivers (like FB-DIMM and RAMBUS ones) will likely report
> + * such DIMMS properly, but the CSROWS-based ones will likely do the wrong
DIMMs csrow-based
> + * thing, as two chip select values are used for dual-rank memories (and 4, for
> + * quad-rank ones). I suspect that this issue could be solved inside the EDAC
> + * core for SDRAM memories, but it requires further study at JEDEC JESD 21C.
The paragraph above is still in, let me repeat my last note:
"This last paragraph sounds innacurately, especially the "likely"
adverbs make it even more confusing. The gist of what you're saying is
already present in the commit message anyway, so drop it here pls."
> + *
> + * In summary, solving this issue is not easy, as it requires a lot of testing.
As before:
"Also superfluous and has nothing to do with edac_mc_alloc()."
Pls remove it.
> * Everything is kmalloc'ed as one big chunk - more efficient.
> * Only can be used if all structures have the same lifetime - otherwise
> @@ -168,22 +196,49 @@ void *edac_align_ptr(void **p, unsigned size, int n_elems)
> *
> * Use edac_mc_free() to free mc structures allocated by this function.
> *
> + * NOTE: drivers handle multi-rank memories in different ways: in some
> + * drivers, one multi-rank memory stick is mapped as one entry, while, in
> + * others, a single multi-rank memory stick would be mapped into several
> + * entries. Currently, this function will allocate multiple struct dimm_info
> + * on such scenarios, as grouping the multiple ranks require drivers change.
> + *
> * Returns:
> * NULL allocation failed
> * struct mem_ctl_info pointer
Ok, this patch still doesn't contain all the changes I requested for
although you said you did them. Is this not the latest version? I'll
wait for you to sort it out before I continue reviewing...
Thanks.
--
Regards/Gruss,
Boris.
Advanced Micro Devices GmbH
Einsteinring 24, 85609 Dornach
GM: Alberto Bozzo
Reg: Dornach, Landkreis Muenchen
HRB Nr. 43632 WEEE Registernr: 129 19551
^ permalink raw reply
* Re: [PATCH EDACv16 1/2] edac: Change internal representation to work with layers
From: Mauro Carvalho Chehab @ 2012-05-04 10:15 UTC (permalink / raw)
To: Borislav Petkov
Cc: Shaohui Xie, Jason Uhlenkott, Aristeu Rozanski, Hitoshi Mitake,
Mark Gross, Dmitry Eremin-Solenikov, Ranganathan Desikan,
Egor Martovetsky, Niklas Söderlund, Tim Small, Arvind R.,
Chris Metcalf, Olof Johansson, Doug Thompson,
Linux Edac Mailing List, Michal Marek, Jiri Kosina,
Linux Kernel Mailing List, Joe Perches, Andrew Morton,
linuxppc-dev
In-Reply-To: <20120504095228.GA18459@aftab.osrc.amd.com>
Em 04-05-2012 06:52, Borislav Petkov escreveu:
> On Thu, May 03, 2012 at 11:16:54AM -0300, Mauro Carvalho Chehab wrote:
>>>> + bool enable_filter,
>>>> + unsigned pos[EDAC_MAX_LAYERS])
>>>
>>> Passing the whole array as an argument instead of only a pointer to it?
>>
>> This is C, and not C++ or Pascal. Only the pointer is passed here. The size
>> of the array is used for type check only.
>
> Right, and you can see where he still has trouble. And by "he" I mean me :).
:)
>
> [ … ]
>
>>>> +void edac_mc_handle_error(const enum hw_event_mc_err_type type,
>>>> + struct mem_ctl_info *mci,
>>>> + const unsigned long page_frame_number,
>>>> + const unsigned long offset_in_page,
>>>> + const unsigned long syndrome,
>>>> + const int layer0,
>>>> + const int layer1,
>>>> + const int layer2,
>>>
>>> Instead of passing each layer as an arg, you can prepare the array pos[]
>>> in each edac_mc_hanlde_*() and pass around a pointer to it - you need it
>>> anyway in the edac_mc_inc*() functions.
>>
>> Yes, but the changes at the drivers will be more complex, without any reason:
>> before each call to this function, they would need to create and fill a temporary
>> array.
>>
>> As there are only 3 layers, in the worse case, this way is simpler and more
>> efficient. We can review it, if we ever need more than 3 layers.
>
> I see, the edac_mc_handle_error is the main interface for all edac drivers, ok.
>
> [ … ]
>
>>>> + bool enable_filter = false;
>>>
>>> What does this enable_filter thing mean:
>>>
>>> if (pos[i] >= 0)
>>> enable_filter = true;
>>>
>>> This absolutely needs explanation and better naming!
>>
>> Renamed it to "enable_per_layer_report".
>
> Or "detailed_dimm_report" or ...
Detail is used on another context; "enable_per_layer_report" won't generate
any doubts for anyone reading the code.
>> The code that implement it seems self-explained:
>>
>> ..
>> if (enable_filter && dimm->nr_pages) {
>> if (p != label) {
>> strcpy(p, OTHER_LABEL);
>> p += strlen(OTHER_LABEL);
>> }
>> strcpy(p, dimm->label);
>> p += strlen(p);
>> *p = '\0';
>>
>> ..
>>
>> if (!enable_filter) {
>> strcpy(label, "any memory");
>> } else {
>> debugf4("%s: csrow/channel to increment: (%d,%d)\n",
>> __func__, row, chan);
>> if (p == label)
>> strcpy(label, "unknown memory");
>> if (type == HW_EVENT_ERR_CORRECTED) {
>> if (row >= 0) {
>> mci->csrows[row].ce_count++;
>> if (chan >= 0)
>> mci->csrows[row].channels[chan].ce_count++;
>> }
>> } else
>> if (row >= 0)
>> mci->csrows[row].ue_count++;
>> }
>>
>> Theis flag indicates if is there any useful information about the affected
>> DIMM(s) provided by the EDAC driver. If this is provided, the DIMM location labels are
>> filtered and reported, and the per-layer error counters are incremented.
>>
>> As it was discussed on previous reviews, with FB-DIMM MCs, and/or when mirror
>> mode/lockstep mode is enabled, the memory controller points errors to 2 DIMMs
>> (or 4 DIMMs, when both mirror mode and lockstep mode are enabled) on most memory
>> controllers, under some conditions. The edac_mc_handle_fbd_ue() function call were
>> created due to that.
>>
>> When comparing with the old code, "enable_filter = false" would be equivalent to call
>> edac_mc_handle_ce_no_info/edac_mc_handle_ue_no_info.
>>
>> I'm adding a comment about it.
>
> Much better, thanks.
>
> Btw, I have to admit, this is a pretty strange way of handling the case
> where layers are { -1, -1, -1 }, i.e. edac_mc_handle_error is called
> with the "no info" hint.
Well, negative values are used on Linux to indicate error conditions, so this is not
that strange. Also, it allows partial "no info", as, on some cases, a channel or
a csrow may not be known. So, this allows code simplification at the drivers.
For example, look at this hunk on the amd64_edac conversion patch:
@@ -1585,16 +1609,10 @@ static void f1x_map_sysaddr_to_csrow(struct mem_ctl_info *mci, u64 sys_addr,
if (dct_ganging_enabled(pvt))
chan = get_channel_from_ecc_syndrome(mci, syndrome);
- if (chan >= 0)
- edac_mc_handle_ce(mci, page, offset, syndrome, csrow, chan,
- EDAC_MOD_STR);
- else
- /*
- * Channel unknown, report all channels on this CSROW as failed.
- */
- for (chan = 0; chan < mci->csrows[csrow].nr_channels; chan++)
- edac_mc_handle_ce(mci, page, offset, syndrome,
- csrow, chan, EDAC_MOD_STR);
+ edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
+ page, offset, syndrome,
+ csrow, chan, -1,
+ EDAC_MOD_STR, "", NULL);
There's no need anymore to check if chan is bigger than 0: if channel is invalid,
the edac_mc_handle_error() will get the DIMM labels for all channels, without needing
to do any loop inside the drivers.
> I'm wondering whether it wouldn't be more readable if you could do
>
> edac_mc_handle_error(HW_EVENT_ERR_INFO_INVALID | ..)
>
> or similar and define such a flag which simply states that. But you'll
> have to change enum hw_event_mc_err_type to a bitfield to allow more
> than one set bit.
>
> Hmm.
>
>
> [ … ]
>
>>> The SCRUB_SW_SRC piece can be another function.
>>
>> It is now part of the edac_ce_error().
>
> Hm, I can't find this function on your "experimental" branch on
> infradead but it is mentioned in the inlined patch below, what's going
> on? Which patch should I be looking at now?
My fault. I forgot to update the push line for the "experimental" remote
at .git/config. I just updated it with the right branch.
The tree on Infradead should now point to the same patch I forwarded at the
ML.
>
> [ … ]
>
>> The following patch addresses the pointed issues. I've updated them
>> on my experimental branch at infradead:
>> git://git.infradead.org/users/mchehab/edac.git experimental
>
> Ok, I checked this one out but can't find the edac_ce_error() function
> as already stated above, pls check.
>
>> They'll also be soon available at:
>> git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-edac.git hw_events_v18
>
> Will review the patch below now and reply in another mail.
Thanks!
Mauro
^ permalink raw reply
* Re: [PATCH EDACv16 1/2] edac: Change internal representation to work with layers
From: Borislav Petkov @ 2012-05-04 9:52 UTC (permalink / raw)
To: Mauro Carvalho Chehab
Cc: Shaohui Xie, Jason Uhlenkott, Aristeu Rozanski, Hitoshi Mitake,
Mark Gross, Dmitry Eremin-Solenikov, Ranganathan Desikan,
Borislav Petkov, Egor Martovetsky, Niklas Söderlund,
Tim Small, Arvind R., Chris Metcalf, Olof Johansson,
Doug Thompson, Linux Edac Mailing List, Michal Marek, Jiri Kosina,
Linux Kernel Mailing List, Joe Perches, Andrew Morton,
linuxppc-dev
In-Reply-To: <4FA29356.3040601@redhat.com>
On Thu, May 03, 2012 at 11:16:54AM -0300, Mauro Carvalho Chehab wrote:
> >> + bool enable_filter,
> >> + unsigned pos[EDAC_MAX_LAYERS])
> >
> > Passing the whole array as an argument instead of only a pointer to it?
>
> This is C, and not C++ or Pascal. Only the pointer is passed here. The size
> of the array is used for type check only.
Right, and you can see where he still has trouble. And by "he" I mean me :).
[ … ]
> >> +void edac_mc_handle_error(const enum hw_event_mc_err_type type,
> >> + struct mem_ctl_info *mci,
> >> + const unsigned long page_frame_number,
> >> + const unsigned long offset_in_page,
> >> + const unsigned long syndrome,
> >> + const int layer0,
> >> + const int layer1,
> >> + const int layer2,
> >
> > Instead of passing each layer as an arg, you can prepare the array pos[]
> > in each edac_mc_hanlde_*() and pass around a pointer to it - you need it
> > anyway in the edac_mc_inc*() functions.
>
> Yes, but the changes at the drivers will be more complex, without any reason:
> before each call to this function, they would need to create and fill a temporary
> array.
>
> As there are only 3 layers, in the worse case, this way is simpler and more
> efficient. We can review it, if we ever need more than 3 layers.
I see, the edac_mc_handle_error is the main interface for all edac drivers, ok.
[ … ]
> >> + bool enable_filter = false;
> >
> > What does this enable_filter thing mean:
> >
> > if (pos[i] >= 0)
> > enable_filter = true;
> >
> > This absolutely needs explanation and better naming!
>
> Renamed it to "enable_per_layer_report".
Or "detailed_dimm_report" or ...
> The code that implement it seems self-explained:
>
> ..
> if (enable_filter && dimm->nr_pages) {
> if (p != label) {
> strcpy(p, OTHER_LABEL);
> p += strlen(OTHER_LABEL);
> }
> strcpy(p, dimm->label);
> p += strlen(p);
> *p = '\0';
>
> ..
>
> if (!enable_filter) {
> strcpy(label, "any memory");
> } else {
> debugf4("%s: csrow/channel to increment: (%d,%d)\n",
> __func__, row, chan);
> if (p == label)
> strcpy(label, "unknown memory");
> if (type == HW_EVENT_ERR_CORRECTED) {
> if (row >= 0) {
> mci->csrows[row].ce_count++;
> if (chan >= 0)
> mci->csrows[row].channels[chan].ce_count++;
> }
> } else
> if (row >= 0)
> mci->csrows[row].ue_count++;
> }
>
> Theis flag indicates if is there any useful information about the affected
> DIMM(s) provided by the EDAC driver. If this is provided, the DIMM location labels are
> filtered and reported, and the per-layer error counters are incremented.
>
> As it was discussed on previous reviews, with FB-DIMM MCs, and/or when mirror
> mode/lockstep mode is enabled, the memory controller points errors to 2 DIMMs
> (or 4 DIMMs, when both mirror mode and lockstep mode are enabled) on most memory
> controllers, under some conditions. The edac_mc_handle_fbd_ue() function call were
> created due to that.
>
> When comparing with the old code, "enable_filter = false" would be equivalent to call
> edac_mc_handle_ce_no_info/edac_mc_handle_ue_no_info.
>
> I'm adding a comment about it.
Much better, thanks.
Btw, I have to admit, this is a pretty strange way of handling the case
where layers are { -1, -1, -1 }, i.e. edac_mc_handle_error is called
with the "no info" hint.
I'm wondering whether it wouldn't be more readable if you could do
edac_mc_handle_error(HW_EVENT_ERR_INFO_INVALID | ..)
or similar and define such a flag which simply states that. But you'll
have to change enum hw_event_mc_err_type to a bitfield to allow more
than one set bit.
Hmm.
[ … ]
> > The SCRUB_SW_SRC piece can be another function.
>
> It is now part of the edac_ce_error().
Hm, I can't find this function on your "experimental" branch on
infradead but it is mentioned in the inlined patch below, what's going
on? Which patch should I be looking at now?
[ … ]
> The following patch addresses the pointed issues. I've updated them
> on my experimental branch at infradead:
> git://git.infradead.org/users/mchehab/edac.git experimental
Ok, I checked this one out but can't find the edac_ce_error() function
as already stated above, pls check.
> They'll also be soon available at:
> git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-edac.git hw_events_v18
Will review the patch below now and reply in another mail.
Thanks.
>
> Regards,
> Mauro
>
> -
>
> edac: Change internal representation to work with layers
>
> From: Mauro Carvalho Chehab <mchehab@redhat.com>
>
> Change the EDAC internal representation to work with non-csrow
> based memory controllers.
--
Regards/Gruss,
Boris.
Advanced Micro Devices GmbH
Einsteinring 24, 85609 Dornach
GM: Alberto Bozzo
Reg: Dornach, Landkreis Muenchen
HRB Nr. 43632 WEEE Registernr: 129 19551
^ permalink raw reply
* Re: [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Benjamin Herrenschmidt @ 2012-05-04 0:10 UTC (permalink / raw)
To: Wang Sheng-Hui
Cc: Stephen Rothwell, linux-kernel, Milton Miller, Anton Blanchard,
linuxppc-dev
In-Reply-To: <4FA31653.5090103@gmail.com>
On Fri, 2012-05-04 at 07:35 +0800, Wang Sheng-Hui wrote:
> > No, Only comment the test, you must absolutely leave the
> > __hard_irq_disable() call ! That's the whole point of the test, make
> > sure we unconditionally disable to see if that fixes the problem, in
> > which case that will tell us that we somewhere accidentally leave
> > irq_happened set to 0x01 while irqs are hard enabled.
>
> It can work.
> My system has been running for about 15 hours without crash.
Ok, so now we need to understand under what circumstances we end up
in a situation where paca->irq_happened is 0x01 and IRQs are hard
enabled. I have a few ideas of things to look at but I'm also off
for the week-end.
I'll have a look next week.
Cheers,
Ben.
^ permalink raw reply
* Re: [PATCH 1/1] page_alloc.c: remove argument to pageblock_default_order
From: Andrew Morton @ 2012-05-03 23:37 UTC (permalink / raw)
To: rajman mekaco
Cc: linux-ia64, Mel Gorman, linux-kernel, linux-mm, Minchan Kim,
Tejun Heo, linuxppc-dev, KAMEZAWA Hiroyuki
In-Reply-To: <1336065312-2891-1-git-send-email-rajman.mekaco@gmail.com>
On Thu, 3 May 2012 22:45:12 +0530
rajman mekaco <rajman.mekaco@gmail.com> wrote:
> When CONFIG_HUGETLB_PAGE_SIZE_VARIABLE is not defined, then
> pageblock_default_order has an argument to it.
>
> However, free_area_init_core will call it without any argument
> anyway.
>
> Remove the argument to pageblock_default_order when
> CONFIG_HUGETLB_PAGE_SIZE_VARIABLE is not defined.
>
> Signed-off-by: rajman mekaco <rajman.mekaco@gmail.com>
> ---
> mm/page_alloc.c | 2 +-
> 1 files changed, 1 insertions(+), 1 deletions(-)
>
> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
> index a712fb9..4b95412 100644
> --- a/mm/page_alloc.c
> +++ b/mm/page_alloc.c
> @@ -4274,7 +4274,7 @@ static inline void __init set_pageblock_order(unsigned int order)
> * at compile-time. See include/linux/pageblock-flags.h for the values of
> * pageblock_order based on the kernel config
> */
> -static inline int pageblock_default_order(unsigned int order)
> +static inline int pageblock_default_order(void)
> {
> return MAX_ORDER-1;
> }
Interesting. It has been that way since at least 3.1.
It didn't break the build because pageblock_default_order() is only
ever invoked by set_pageblock_order(), with:
set_pageblock_order(pageblock_default_order());
and set_pageblock_order() is a macro:
#define set_pageblock_order(x) do {} while (0)
There's yet another reason not to use macros, dammit - they hide bugs.
Mel, can you have a think about this please? Can we just kill off
pageblock_default_order() and fold its guts into
set_pageblock_order(void)? Only ia64 and powerpc can define
CONFIG_HUGETLB_PAGE_SIZE_VARIABLE.
--- a/mm/page_alloc.c~a
+++ a/mm/page_alloc.c
@@ -4300,25 +4300,24 @@ static inline void setup_usemap(struct p
#ifdef CONFIG_HUGETLB_PAGE_SIZE_VARIABLE
-/* Return a sensible default order for the pageblock size. */
-static inline int pageblock_default_order(void)
-{
- if (HPAGE_SHIFT > PAGE_SHIFT)
- return HUGETLB_PAGE_ORDER;
-
- return MAX_ORDER-1;
-}
-
/* Initialise the number of pages represented by NR_PAGEBLOCK_BITS */
-static inline void __init set_pageblock_order(unsigned int order)
+static inline void __init set_pageblock_order(void)
{
+ unsigned int order;
+
/* Check that pageblock_nr_pages has not already been setup */
if (pageblock_order)
return;
+ if (HPAGE_SHIFT > PAGE_SHIFT)
+ order = HUGETLB_PAGE_ORDER;
+ else
+ order = MAX_ORDER - 1;
+
/*
* Assume the largest contiguous order of interest is a huge page.
- * This value may be variable depending on boot parameters on IA64
+ * This value may be variable depending on boot parameters on IA64 and
+ * powerpc.
*/
pageblock_order = order;
}
@@ -4326,15 +4325,13 @@ static inline void __init set_pageblock_
/*
* When CONFIG_HUGETLB_PAGE_SIZE_VARIABLE is not set, set_pageblock_order()
- * and pageblock_default_order() are unused as pageblock_order is set
- * at compile-time. See include/linux/pageblock-flags.h for the values of
- * pageblock_order based on the kernel config
+ * is unused as pageblock_order is set at compile-time. See
+ * include/linux/pageblock-flags.h for the values of pageblock_order based on
+ * the kernel config
*/
-static inline int pageblock_default_order(unsigned int order)
+static inline void set_pageblock_order(void)
{
- return MAX_ORDER-1;
}
-#define set_pageblock_order(x) do {} while (0)
#endif /* CONFIG_HUGETLB_PAGE_SIZE_VARIABLE */
@@ -4422,7 +4419,7 @@ static void __paginginit free_area_init_
if (!size)
continue;
- set_pageblock_order(pageblock_default_order());
+ set_pageblock_order();
setup_usemap(pgdat, zone, size);
ret = init_currently_empty_zone(zone, zone_start_pfn,
size, MEMMAP_EARLY);
_
^ permalink raw reply
* Re: [PATCH] powerpc: use local var instead of local_paca->irq_happened directly in __check_irq_replay
From: Wang Sheng-Hui @ 2012-05-03 23:35 UTC (permalink / raw)
To: Benjamin Herrenschmidt
Cc: Stephen Rothwell, linux-kernel, Milton Miller, Anton Blanchard,
linuxppc-dev
In-Reply-To: <1336032561.18712.1.camel@pasglop>
On 2012年05月03日 16:09, Benjamin Herrenschmidt wrote:
> On Thu, 2012-05-03 at 14:59 +0800, Wang Sheng-Hui wrote:
>> On 2012年05月03日 14:33, Wang Sheng-Hui wrote:
>>> if (unlikely(irq_happened != PACA_IRQ_HARD_DIS))
>>>> __hard_irq_disable();
>>
>> I have commented out the 2 lines.
>
> No, Only comment the test, you must absolutely leave the
> __hard_irq_disable() call ! That's the whole point of the test, make
> sure we unconditionally disable to see if that fixes the problem, in
> which case that will tell us that we somewhere accidentally leave
> irq_happened set to 0x01 while irqs are hard enabled.
It can work.
My system has been running for about 15 hours without crash.
>
> Cheers,
> Ben.
>
>
^ permalink raw reply
* Re: [PATCH v16 01/10]USB/ppc4xx: Add Synopsys DWC OTG Register definitions
From: Wolfgang Denk @ 2012-05-03 18:30 UTC (permalink / raw)
To: Rupjyoti Sarmah; +Cc: linuxppc-dev, rsarmah, linux-kernel
In-Reply-To: <201205031223.q43CN3LN022598@amcc.com>
Dear Rupjyoti Sarmah,
In message <201205031223.q43CN3LN022598@amcc.com> you wrote:
>
> Add Synopsys Design Ware core register definitions.
Olof Johansson <olof@lixom.net> has commented v15 of this patch as
follows:
> No, just start over from scratch. Just leave the crap driver behind,
> use it for reference but write the new one.
>
> It's obvious given that you are already at iteration v15 and it's
> still looking this bad that this is not realistic to get reviewed and
> accepted as-is. I don't think staging is a good target either -- what
> the driver really needs is _functional_ cut-down to only cover the use
> cases that your product uses, and staging cleanups are mostly around
> style and refactoring, not changing, fixing or removing functionality.
> ...
> I don't think you understood what I meant. Try building an ARM config
> with this driver enabled, for example, and you'll see that it breaks
> the build.
See http://thread.gmane.org/gmane.linux.usb.general/53348/focus=53913
for the full context and other important comments.
It seems most of these requests have been ignored so far.
I would also like to point out that the same Synopsys USB controller
is used in a number of other SoCs (especially ARM chips), and
supported by other drivers, some of these even in mainline.
See http://thread.gmane.org/gmane.linux.usb.general/61714/focus=62139
for a related thread.
Instead of trying to add a completely new driver to mainline (and one
which has been repeatedly been rejected), I vote for focussing on the
existing driver code that is already in mainline, and testing and
improving this so we can use a single implementation of this driver
code for all SoCs that use the same IP block.
Best regards,
Wolfgang Denk
--
DENX Software Engineering GmbH, MD: Wolfgang Denk & Detlev Zundel
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: (+49)-8142-66989-10 Fax: (+49)-8142-66989-80 Email: wd@denx.de
panic: kernel trap (ignored)
^ permalink raw reply
* Re: [PATCH EDACv16 1/2] edac: Change internal representation to work with layers
From: Mauro Carvalho Chehab @ 2012-05-03 14:16 UTC (permalink / raw)
To: Borislav Petkov
Cc: Shaohui Xie, Jason Uhlenkott, Aristeu Rozanski, Hitoshi Mitake,
Mark Gross, Dmitry Eremin-Solenikov, Ranganathan Desikan,
Egor Martovetsky, Niklas Söderlund, Tim Small, Arvind R.,
Chris Metcalf, Olof Johansson, Doug Thompson,
Linux Edac Mailing List, Michal Marek, Jiri Kosina,
Linux Kernel Mailing List, Joe Perches, Andrew Morton,
linuxppc-dev
In-Reply-To: <20120502133052.GE12914@aftab.osrc.amd.com>
Em 02-05-2012 10:30, Borislav Petkov escreveu:
> Starting a new thread because the old one grew too big and
> is out of my screen. Patch below is git-formatted from
> git://git.infradead.org/users/mchehab/edac.git
>
>> commit 447b7929e633027ffe131f2f8f246bba5690cee7
>> Author: Mauro Carvalho Chehab <mchehab@redhat.com>
>> Date: Wed Apr 18 15:20:50 2012 -0300
>>
>> edac: Change internal representation to work with layers
>>
>> Change the EDAC internal representation to work with non-csrow
>> based memory controllers.
>>
>> There are lots of those memory controllers nowadays, and more
>> are coming. So, the EDAC internal representation needs to be
>> changed, in order to work with those memory controllers, while
>> preserving backward compatibility with the old ones.
>>
>> The edac core was written with the idea that memory controllers
>> are able to directly access csrows.
>>
>> This is not true for FB-DIMM and RAMBUS memory controllers.
>>
>> Also, some recent advanced memory controllers don't present a per-csrows
>> view. Instead, they view memories as DIMMs, instead of ranks.
>>
>> So, change the allocation and error report routines to allow
>> them to work with all types of architectures.
>>
>> This will allow the removal of several hacks with FB-DIMM and RAMBUS
>> memory controllers.
>>
>> Also, several tests were done on different platforms using different
>> x86 drivers.
>>
>> TODO: a multi-rank DIMMs are currently represented by multiple DIMM
>> entries in struct dimm_info. That means that changing a label for one
>> rank won't change the same label for the other ranks at the same DIMM.
>> This bug is present since the beginning of the EDAC, so it is not a big
>> deal. However, on several drivers, it is possible to fix this issue, but
>> it should be a per-driver fix, as the csrow => DIMM arrangement may not
>> be equal for all. So, don't try to fix it here yet.
>>
>> I tried to make this patch as short as possible, preceding it with
>> several other patches that simplified the logic here. Yet, as the
>> internal API changes, all drivers need changes. The changes are
>> generally bigger in the drivers for FB-DIMMs.
>
> <snip already reviewed stuff>
>
>> /* Figure out the offsets of the various items from the start of an mc
>> * structure. We want the alignment of each item to be at least as
>> @@ -191,12 +253,28 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
>> * hardcode everything into a single struct.
>> */
>> mci = edac_align_ptr(&ptr, sizeof(*mci), 1);
>> - csi = edac_align_ptr(&ptr, sizeof(*csi), nr_csrows);
>> - chi = edac_align_ptr(&ptr, sizeof(*chi), nr_csrows * nr_chans);
>> - dimm = edac_align_ptr(&ptr, sizeof(*dimm), nr_csrows * nr_chans);
>> + layer = edac_align_ptr(&ptr, sizeof(*layer), n_layers);
>> + csi = edac_align_ptr(&ptr, sizeof(*csi), tot_csrows);
>> + chi = edac_align_ptr(&ptr, sizeof(*chi), tot_csrows * tot_channels);
>> + dimm = edac_align_ptr(&ptr, sizeof(*dimm), tot_dimms);
>> + count = 1;
>> + for (i = 0; i < n_layers; i++) {
>> + count *= layers[i].size;
>> + debugf4("%s: errcount layer %d size %d\n", __func__, i, count);
>> + ce_per_layer[i] = edac_align_ptr(&ptr, sizeof(u32), count);
>> + ue_per_layer[i] = edac_align_ptr(&ptr, sizeof(u32), count);
>> + tot_errcount += 2 * count;
>> + }
>> +
>> + debugf4("%s: allocating %d error counters\n", __func__, tot_errcount);
>> pvt = edac_align_ptr(&ptr, sz_pvt, 1);
>> size = ((unsigned long)pvt) + sz_pvt;
>>
>> + debugf1("%s(): allocating %u bytes for mci data (%d %s, %d csrows/channels)\n",
>> + __func__, size,
>> + tot_dimms,
>> + per_rank ? "ranks" : "dimms",
>> + tot_csrows * tot_channels);
>> mci = kzalloc(size, GFP_KERNEL);
>> if (mci == NULL)
>> return NULL;
>> @@ -204,42 +282,101 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
>> /* Adjust pointers so they point within the memory we just allocated
>> * rather than an imaginary chunk of memory located at address 0.
>> */
>> + layer = (struct edac_mc_layer *)(((char *)mci) + ((unsigned long)layer));
>> csi = (struct csrow_info *)(((char *)mci) + ((unsigned long)csi));
>> chi = (struct rank_info *)(((char *)mci) + ((unsigned long)chi));
>> dimm = (struct dimm_info *)(((char *)mci) + ((unsigned long)dimm));
>> + for (i = 0; i < n_layers; i++) {
>> + mci->ce_per_layer[i] = (u32 *)((char *)mci + ((unsigned long)ce_per_layer[i]));
>> + mci->ue_per_layer[i] = (u32 *)((char *)mci + ((unsigned long)ue_per_layer[i]));
>> + }
>> pvt = sz_pvt ? (((char *)mci) + ((unsigned long)pvt)) : NULL;
>>
>> /* setup index and various internal pointers */
>> mci->mc_idx = edac_index;
>> mci->csrows = csi;
>> mci->dimms = dimm;
>> + mci->tot_dimms = tot_dimms;
>> mci->pvt_info = pvt;
>> - mci->nr_csrows = nr_csrows;
>> + mci->n_layers = n_layers;
>> + mci->layers = layer;
>> + memcpy(mci->layers, layers, sizeof(*layer) * n_layers);
>> + mci->nr_csrows = tot_csrows;
>> + mci->num_cschannel = tot_channels;
>> + mci->mem_is_per_rank = per_rank;
>>
>> /*
>> - * For now, assumes that a per-csrow arrangement for dimms.
>> - * This will be latter changed.
>> + * Fills the csrow struct
>
> Fill
>
>> */
>> - dimm = mci->dimms;
>> -
>> - for (row = 0; row < nr_csrows; row++) {
>> - csrow = &csi[row];
>> - csrow->csrow_idx = row;
>> - csrow->mci = mci;
>> - csrow->nr_channels = nr_chans;
>> - chp = &chi[row * nr_chans];
>> - csrow->channels = chp;
>> -
>> - for (chn = 0; chn < nr_chans; chn++) {
>> + for (row = 0; row < tot_csrows; row++) {
>> + csr = &csi[row];
>> + csr->csrow_idx = row;
>> + csr->mci = mci;
>> + csr->nr_channels = tot_channels;
>> + chp = &chi[row * tot_channels];
>> + csr->channels = chp;
>> +
>> + for (chn = 0; chn < tot_channels; chn++) {
>> chan = &chp[chn];
>> chan->chan_idx = chn;
>> - chan->csrow = csrow;
>> + chan->csrow = csr;
>> + }
>> + }
>>
>> - mci->csrows[row].channels[chn].dimm = dimm;
>> - dimm->csrow = row;
>> - dimm->csrow_channel = chn;
>> - dimm++;
>> - mci->nr_dimms++;
>> + /*
>> + * Fills the dimm struct
>
> Fill
>
>> + */
>> + memset(&pos, 0, sizeof(pos));
>> + row = 0;
>> + chn = 0;
>> + debugf4("%s: initializing %d %s\n", __func__, tot_dimms,
>> + per_rank ? "ranks" : "dimms");
>> + for (i = 0; i < tot_dimms; i++) {
>> + chan = &csi[row].channels[chn];
>> + dimm = EDAC_DIMM_PTR(layer, mci->dimms, n_layers,
>> + pos[0], pos[1], pos[2]);
>> + dimm->mci = mci;
>> +
>> + debugf2("%s: %d: %s%zd (%d:%d:%d): row %d, chan %d\n", __func__,
>> + i, per_rank ? "rank" : "dimm", (dimm - mci->dimms),
>> + pos[0], pos[1], pos[2], row, chn);
>> +
>> + /* Copy DIMM location */
>> + for (j = 0; j < n_layers; j++)
>> + dimm->location[j] = pos[j];
>> +
>> + /* Link it to the csrows old API data */
>> + chan->dimm = dimm;
>> + dimm->csrow = row;
>> + dimm->cschannel = chn;
>> +
>> + /* Increment csrow location */
>> + if (!rev_order) {
>
> AFAICT, this rev_order is always false (in the final version of the
> patches anyway) and if so, can be completely removed as an argument to
> edac_mc_alloc() and also the code in the else-branch below.
>
>> + for (j = n_layers - 1; j >= 0; j--)
>> + if (!layers[j].is_virt_csrow)
>> + break;
>> + chn++;
>> + if (chn == tot_channels) {
>> + chn = 0;
>> + row++;
>> + }
>> + } else {
>> + for (j = n_layers - 1; j >= 0; j--)
>> + if (layers[j].is_virt_csrow)
>> + break;
>> + row++;
>> + if (row == tot_csrows) {
>> + row = 0;
>> + chn++;
>> + }
>> + }
>> +
>> + /* Increment dimm location */
>> + for (j = n_layers - 1; j >= 0; j--) {
>> + pos[j]++;
>> + if (pos[j] < layers[j].size)
>> + break;
>> + pos[j] = 0;
>> }
>> }
>>
>> @@ -263,6 +400,57 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
>> */
>> return mci;
>> }
>> +EXPORT_SYMBOL_GPL(new_edac_mc_alloc);
>> +
>> +/**
>> + * edac_mc_alloc: Allocate and partially fills a struct mem_ctl_info structure
>
> fill
>
>> + * @edac_index: Memory controller number
>
> How about 'mc_num' for a variable name instead then? I know, I know, it
> was called like that originally but 'edac_index' is misleading.
>
>> + * @n_layers: Nu
>> +mber of layers at the MC hierarchy
>
> needless '\n'
>
>> + * layers: Describes each layer as seen by the Memory Controller
>> + * @rev_order: Fills csrows/cs channels at the reverse order
>
> you can drop that, as put above.
>
>> + * @size_pvt: size of private storage needed
>
> Capitalize: Size
>
>> + *
>> + *
>> + * FIXME: drivers handle multi-rank memories on different ways: on some
>
> in (remove "on")
>
>> + * drivers, one multi-rank memory is mapped as one DIMM, while, on others,
>
> and say: "Some drivers map multi-ranked DIMMs as one DIMM while others
> as several DIMMs".
>
>> + * a single multi-rank DIMM would be mapped into several "dimms".
>> + *
>> + * Non-csrow based drivers (like FB-DIMM and RAMBUS ones) will likely report
>> + * such DIMMS properly, but the csrow-based ones will likely do the wrong
>> + * thing, as two chip select values are used for dual-rank memories (and 4, for
>> + * quad-rank ones). I suspect that this issue could be solved inside the EDAC
>> + * core for SDRAM memories, but it requires further study at JEDEC JESD 21C.
>
> This last paragraph sounds innacurately, especially the "likely" adverbs
> make it even more confusing. The gist of what you're saying is already
> present in the commit message anyway, so drop it here pls.
>
>> + *
>> + * In summary, solving this issue is not easy, as it requires a lot of testing.
>
> Also superfluous and has nothing to do with edac_mc_alloc().
>
>> + *
>> + * Everything is kmalloc'ed as one big chunk - more efficient.
>> + * Only can be used if all structures have the same lifetime - otherwisea
>
> "It can only be used if ..."
>
>> + * you have to allocate and initialize your own structures.
>> + *
>> + * Use edac_mc_free() to free mc structures allocated by this function.
>> + *
>> + * Returns:
>> + * NULL allocation failed
>> + * struct mem_ctl_info pointer
>
> On failure: NULL
> On success: struct mem_ctl_info.
>
>> + */
>> +
>> +struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
>> + unsigned nr_chans, int edac_index)
>> +{
>> + unsigned n_layers = 2;
>> + struct edac_mc_layer layers[n_layers];
>> +
>> + layers[0].type = EDAC_MC_LAYER_CHIP_SELECT;
>> + layers[0].size = nr_csrows;
>> + layers[0].is_virt_csrow = true;
>> + layers[1].type = EDAC_MC_LAYER_CHANNEL;
>> + layers[1].size = nr_chans;
>> + layers[1].is_virt_csrow = false;
>> +
>> + return new_edac_mc_alloc(edac_index, ARRAY_SIZE(layers), layers,
>> + false, sz_pvt);
>> +}
>> EXPORT_SYMBOL_GPL(edac_mc_alloc);
>>
>> /**
>> @@ -528,7 +716,6 @@ EXPORT_SYMBOL(edac_mc_find);
>> * edac_mc_add_mc: Insert the 'mci' structure into the mci global list and
>> * create sysfs entries associated with mci structure
>> * @mci: pointer to the mci structure to be added to the list
>> - * @mc_idx: A unique numeric identifier to be assigned to the 'mci' structure.
>> *
>> * Return:
>> * 0 Success
>> @@ -555,6 +742,8 @@ int edac_mc_add_mc(struct mem_ctl_info *mci)
>> edac_mc_dump_channel(&mci->csrows[i].
>> channels[j]);
>> }
>> + for (i = 0; i < mci->tot_dimms; i++)
>> + edac_mc_dump_dimm(&mci->dimms[i]);
>> }
>> #endif
>> mutex_lock(&mem_ctls_mutex);
>> @@ -712,261 +901,251 @@ int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci, unsigned long page)
>> }
>> EXPORT_SYMBOL_GPL(edac_mc_find_csrow_by_page);
>>
>> -/* FIXME - setable log (warning/emerg) levels */
>> -/* FIXME - integrate with evlog: http://evlog.sourceforge.net/ */
>> -void edac_mc_handle_ce(struct mem_ctl_info *mci,
>> - unsigned long page_frame_number,
>> - unsigned long offset_in_page, unsigned long syndrome,
>> - int row, int channel, const char *msg)
>> +const char *edac_layer_name[] = {
>> + [EDAC_MC_LAYER_BRANCH] = "branch",
>> + [EDAC_MC_LAYER_CHANNEL] = "channel",
>> + [EDAC_MC_LAYER_SLOT] = "slot",
>> + [EDAC_MC_LAYER_CHIP_SELECT] = "csrow",
>> +};
>> +EXPORT_SYMBOL_GPL(edac_layer_name);
>> +
>> +static void edac_increment_ce_error(struct mem_ctl_info *mci,
>
> This could be abbreviated to edac_inc_ce_error()
All the above changed, as requested.
>> + bool enable_filter,
>> + unsigned pos[EDAC_MAX_LAYERS])
>
> Passing the whole array as an argument instead of only a pointer to it?
This is C, and not C++ or Pascal. Only the pointer is passed here. The size
of the array is used for type check only.
>> {
>> - unsigned long remapped_page;
>> - char *label = NULL;
>> - u32 grain;
>> + int i, index = 0;
>>
>> - debugf3("MC%d: %s()\n", mci->mc_idx, __func__);
>> + mci->ce_mc++;
>>
>> - /* FIXME - maybe make panic on INTERNAL ERROR an option */
>> - if (row >= mci->nr_csrows || row < 0) {
>> - /* something is wrong */
>> - edac_mc_printk(mci, KERN_ERR,
>> - "INTERNAL ERROR: row out of range "
>> - "(%d >= %d)\n", row, mci->nr_csrows);
>> - edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR");
>> + if (!enable_filter) {
>> + mci->ce_noinfo_count++;
>> return;
>> }
>>
>> - if (channel >= mci->csrows[row].nr_channels || channel < 0) {
>> - /* something is wrong */
>> - edac_mc_printk(mci, KERN_ERR,
>> - "INTERNAL ERROR: channel out of range "
>> - "(%d >= %d)\n", channel,
>> - mci->csrows[row].nr_channels);
>> - edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR");
>> - return;
>> - }
>> -
>> - label = mci->csrows[row].channels[channel].dimm->label;
>> - grain = mci->csrows[row].channels[channel].dimm->grain;
>> + for (i = 0; i < mci->n_layers; i++) {
>> + if (pos[i] < 0)
>> + break;
>> + index += pos[i];
>> + mci->ce_per_layer[i][index]++;
>>
>> - if (edac_mc_get_log_ce())
>> - /* FIXME - put in DIMM location */
>> - edac_mc_printk(mci, KERN_WARNING,
>> - "CE page 0x%lx, offset 0x%lx, grain %d, syndrome "
>> - "0x%lx, row %d, channel %d, label \"%s\": %s\n",
>> - page_frame_number, offset_in_page,
>> - grain, syndrome, row, channel,
>> - label, msg);
>> + if (i < mci->n_layers - 1)
>> + index *= mci->layers[i + 1].size;
>> + }
>> +}
>>
>> - mci->ce_count++;
>> - mci->csrows[row].ce_count++;
>> - mci->csrows[row].channels[channel].dimm->ce_count++;
>> - mci->csrows[row].channels[channel].ce_count++;
>> +static void edac_increment_ue_error(struct mem_ctl_info *mci,
>> + bool enable_filter,
>> + unsigned pos[EDAC_MAX_LAYERS])
>> +{
>> + int i, index = 0;
>>
>> - if (mci->scrub_mode & SCRUB_SW_SRC) {
>> - /*
>> - * Some MC's can remap memory so that it is still available
>> - * at a different address when PCI devices map into memory.
>> - * MC's that can't do this lose the memory where PCI devices
>> - * are mapped. This mapping is MC dependent and so we call
>> - * back into the MC driver for it to map the MC page to
>> - * a physical (CPU) page which can then be mapped to a virtual
>> - * page - which can then be scrubbed.
>> - */
>> - remapped_page = mci->ctl_page_to_phys ?
>> - mci->ctl_page_to_phys(mci, page_frame_number) :
>> - page_frame_number;
>> + mci->ue_mc++;
>>
>> - edac_mc_scrub_block(remapped_page, offset_in_page, grain);
>> + if (!enable_filter) {
>> + mci->ce_noinfo_count++;
>> + return;
>> }
>> -}
>> -EXPORT_SYMBOL_GPL(edac_mc_handle_ce);
>>
>> -void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci, const char *msg)
>> -{
>> - if (edac_mc_get_log_ce())
>> - edac_mc_printk(mci, KERN_WARNING,
>> - "CE - no information available: %s\n", msg);
>> + for (i = 0; i < mci->n_layers; i++) {
>> + if (pos[i] < 0)
>> + break;
>> + index += pos[i];
>> + mci->ue_per_layer[i][index]++;
>>
>> - mci->ce_noinfo_count++;
>> - mci->ce_count++;
>> + if (i < mci->n_layers - 1)
>> + index *= mci->layers[i + 1].size;
>> + }
>> }
>> -EXPORT_SYMBOL_GPL(edac_mc_handle_ce_no_info);
>>
>> -void edac_mc_handle_ue(struct mem_ctl_info *mci,
>> - unsigned long page_frame_number,
>> - unsigned long offset_in_page, int row, const char *msg)
>> +#define OTHER_LABEL " or "
>> +void edac_mc_handle_error(const enum hw_event_mc_err_type type,
>> + struct mem_ctl_info *mci,
>> + const unsigned long page_frame_number,
>> + const unsigned long offset_in_page,
>> + const unsigned long syndrome,
>> + const int layer0,
>> + const int layer1,
>> + const int layer2,
>
> Instead of passing each layer as an arg, you can prepare the array pos[]
> in each edac_mc_hanlde_*() and pass around a pointer to it - you need it
> anyway in the edac_mc_inc*() functions.
Yes, but the changes at the drivers will be more complex, without any reason:
before each call to this function, they would need to create and fill a temporary
array.
As there are only 3 layers, in the worse case, this way is simpler and more
efficient. We can review it, if we ever need more than 3 layers.
>
>> + const char *msg,
>> + const char *other_detail,
>> + const void *mcelog)
>
> Also, this function is huuuge and begs to be splitted into small,
> self-contained helpers.
broken into a few more helper functions.
>
>> {
>> - int len = EDAC_MC_LABEL_LEN * 4;
>> - char labels[len + 1];
>> - char *pos = labels;
>> - int chan;
>> - int chars;
>> - char *label = NULL;
>> + unsigned long remapped_page;
>> + /* FIXME: too much for stack: move it to some pre-alocated area */
>> + char detail[80], location[80];
>> + char label[(EDAC_MC_LABEL_LEN + 1 + sizeof(OTHER_LABEL)) * mci->tot_dimms];
>> + char *p;
>> + int row = -1, chan = -1;
>> + int pos[EDAC_MAX_LAYERS] = { layer0, layer1, layer2 };
>> + int i;
>> u32 grain;
>> + bool enable_filter = false;
>
> What does this enable_filter thing mean:
>
> if (pos[i] >= 0)
> enable_filter = true;
>
> This absolutely needs explanation and better naming!
Renamed it to "enable_per_layer_report".
The code that implement it seems self-explained:
..
if (enable_filter && dimm->nr_pages) {
if (p != label) {
strcpy(p, OTHER_LABEL);
p += strlen(OTHER_LABEL);
}
strcpy(p, dimm->label);
p += strlen(p);
*p = '\0';
..
if (!enable_filter) {
strcpy(label, "any memory");
} else {
debugf4("%s: csrow/channel to increment: (%d,%d)\n",
__func__, row, chan);
if (p == label)
strcpy(label, "unknown memory");
if (type == HW_EVENT_ERR_CORRECTED) {
if (row >= 0) {
mci->csrows[row].ce_count++;
if (chan >= 0)
mci->csrows[row].channels[chan].ce_count++;
}
} else
if (row >= 0)
mci->csrows[row].ue_count++;
}
Theis flag indicates if is there any useful information about the affected
DIMM(s) provided by the EDAC driver. If this is provided, the DIMM location labels are
filtered and reported, and the per-layer error counters are incremented.
As it was discussed on previous reviews, with FB-DIMM MCs, and/or when mirror
mode/lockstep mode is enabled, the memory controller points errors to 2 DIMMs
(or 4 DIMMs, when both mirror mode and lockstep mode are enabled) on most memory
controllers, under some conditions. The edac_mc_handle_fbd_ue() function call were
created due to that.
When comparing with the old code, "enable_filter = false" would be equivalent to call
edac_mc_handle_ce_no_info/edac_mc_handle_ue_no_info.
I'm adding a comment about it.
>
>>
>> debugf3("MC%d: %s()\n", mci->mc_idx, __func__);
>>
>> - /* FIXME - maybe make panic on INTERNAL ERROR an option */
>> - if (row >= mci->nr_csrows || row < 0) {
>> - /* something is wrong */
>> - edac_mc_printk(mci, KERN_ERR,
>> - "INTERNAL ERROR: row out of range "
>> - "(%d >= %d)\n", row, mci->nr_csrows);
>> - edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR");
>> - return;
>> - }
>> -
>> - grain = mci->csrows[row].channels[0].dimm->grain;
>> - label = mci->csrows[row].channels[0].dimm->label;
>> - chars = snprintf(pos, len + 1, "%s", label);
>> - len -= chars;
>> - pos += chars;
>> -
>> - for (chan = 1; (chan < mci->csrows[row].nr_channels) && (len > 0);
>> - chan++) {
>> - label = mci->csrows[row].channels[chan].dimm->label;
>> - chars = snprintf(pos, len + 1, ":%s", label);
>> - len -= chars;
>> - pos += chars;
>> + /* Check if the event report is consistent */
>> + for (i = 0; i < mci->n_layers; i++) {
>> + if (pos[i] >= (int)mci->layers[i].size) {
>> + if (type == HW_EVENT_ERR_CORRECTED) {
>> + p = "CE";
>> + mci->ce_mc++;
>> + } else {
>> + p = "UE";
>> + mci->ue_mc++;
>
> Ok, mci->ce_mc and mci->ue_mc are being incremented here and the same
> happens below again in the edac_inc*_{ce,ue}_error() functions below.
>
> Why? Current layer is within valid elements count of current layer?
Fixed. This is a bug introduced by one of the rebases: In the past, there
was two error counters out there, one for the legacy API, and another one
for the new API.
>
>> + }
>> + edac_mc_printk(mci, KERN_ERR,
>> + "INTERNAL ERROR: %s value is out of range (%d >= %d)\n",
>> + edac_layer_name[mci->layers[i].type],
>> + pos[i], mci->layers[i].size);
>> + /*
>> + * Instead of just returning it, let's use what's
>> + * known about the error. The increment routines and
>> + * the DIMM filter logic will do the right thing by
>> + * pointing the likely damaged DIMMs.
>> + */
>> + pos[i] = -1;
>> + }
>> + if (pos[i] >= 0)
>> + enable_filter = true;
>
> As above, what does that filter logic mean, where it is explained?
Se above.
>
>>
>> - if (edac_mc_get_log_ue())
>> - edac_mc_printk(mci, KERN_EMERG,
>> - "UE page 0x%lx, offset 0x%lx, grain %d, row %d, "
>> - "labels \"%s\": %s\n", page_frame_number,
>> - offset_in_page, grain, row, labels, msg);
>> -
>> - if (edac_mc_get_panic_on_ue())
>> - panic("EDAC MC%d: UE page 0x%lx, offset 0x%lx, grain %d, "
>> - "row %d, labels \"%s\": %s\n", mci->mc_idx,
>> - page_frame_number, offset_in_page,
>> - grain, row, labels, msg);
>> -
>> - mci->ue_count++;
>> - mci->csrows[row].ue_count++;
>> -}
>> -EXPORT_SYMBOL_GPL(edac_mc_handle_ue);
>> + /*
>> + * Get the dimm label/grain that applies to the match criteria.
>> + * As the error algorithm may not be able to point to just one memory,
>
> What exactly do you mean by "memory" here? DIMM, slot, rank? Please be
> more specific.
A memory stick. As I said above, and on a previous review, some chipkill
algorithms/logic may point to 2 or 4 memory sticks, depending if lockstep
mode and/or mirror mode is enabled.
Added "stick" to the comment.
>> + * the logic here will get all possible labels that could pottentially
>> + * be affected by the error.
>> + * On FB-DIMM memory controllers, for uncorrected errors, it is common
>> + * to have only the MC channel and the MC dimm (also called as "rank")
>
> remove "as"
Hmm... actually it should be, instead:
(also called "branch")
Fixed.
>
>> + * but the channel is not known, as the memory is arranged in pairs,
>> + * where each memory belongs to a separate channel within the same
>> + * branch.
>> + * It will also get the max grain, over the error match range
>> + */
>> + grain = 0;
>> + p = label;
>> + *p = '\0';
>> + for (i = 0; i < mci->tot_dimms; i++) {
>> + struct dimm_info *dimm = &mci->dimms[i];
>>
>> -void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci, const char *msg)
>> -{
>> - if (edac_mc_get_panic_on_ue())
>> - panic("EDAC MC%d: Uncorrected Error", mci->mc_idx);
>> + if (layer0 >= 0 && layer0 != dimm->location[0])
>> + continue;
>> + if (layer1 >= 0 && layer1 != dimm->location[1])
>> + continue;
>> + if (layer2 >= 0 && layer2 != dimm->location[2])
>> + continue;
>>
>> - if (edac_mc_get_log_ue())
>> - edac_mc_printk(mci, KERN_WARNING,
>> - "UE - no information available: %s\n", msg);
>> - mci->ue_noinfo_count++;
>> - mci->ue_count++;
>> -}
>> -EXPORT_SYMBOL_GPL(edac_mc_handle_ue_no_info);
>> + if (dimm->grain > grain)
>> + grain = dimm->grain;
>
> Pls move the "max grain" part of the comment over this lines so that one
> knows what happens.
Ok.
>
>>
>> -/*************************************************************
>> - * On Fully Buffered DIMM modules, this help function is
>> - * called to process UE events
>> - */
>> -void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci,
>> - unsigned int csrow,
>> - unsigned int channela,
>> - unsigned int channelb, char *msg)
>> -{
>> - int len = EDAC_MC_LABEL_LEN * 4;
>> - char labels[len + 1];
>> - char *pos = labels;
>> - int chars;
>> - char *label;
>> -
>> - if (csrow >= mci->nr_csrows) {
>> - /* something is wrong */
>> - edac_mc_printk(mci, KERN_ERR,
>> - "INTERNAL ERROR: row out of range (%d >= %d)\n",
>> - csrow, mci->nr_csrows);
>> - edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR");
>> - return;
>> + /*
>> + * If the error is memory-controller wide, there's no sense
>> + * on seeking for the affected DIMMs, as everything may be
>
> "there's no need to seek for the affected DIMMs because the whole
> channel/memory controller/... may be affected"
>
>> + * affected. Also, don't show errors for non-filled dimm's.
>
> for empty DIMM slots.
>
>> + */
>> + if (enable_filter && dimm->nr_pages) {
>> + if (p != label) {
>> + strcpy(p, OTHER_LABEL);
>> + p += strlen(OTHER_LABEL);
>> + }
>> + strcpy(p, dimm->label);
>> + p += strlen(p);
>> + *p = '\0';
>> +
>> + /*
>> + * get csrow/channel of the dimm, in order to allow
>
> DIMM
>
>> + * incrementing the compat API counters
>> + */
>> + debugf4("%s: %s csrows map: (%d,%d)\n",
>> + __func__,
>> + mci->mem_is_per_rank ? "rank" : "dimm",
>> + dimm->csrow, dimm->cschannel);
>
> newline
>
>> + if (row == -1)
>> + row = dimm->csrow;
>> + else if (row >= 0 && row != dimm->csrow)
>> + row = -2;
>
> ditto
>
>> + if (chan == -1)
>> + chan = dimm->cschannel;
>> + else if (chan >= 0 && chan != dimm->cschannel)
>> + chan = -2;
>> + }
>> }
>> -
>> - if (channela >= mci->csrows[csrow].nr_channels) {
>> - /* something is wrong */
>> - edac_mc_printk(mci, KERN_ERR,
>> - "INTERNAL ERROR: channel-a out of range "
>> - "(%d >= %d)\n",
>> - channela, mci->csrows[csrow].nr_channels);
>> - edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR");
>> - return;
>
> newline here.
>
>> + if (!enable_filter) {
>> + strcpy(label, "any memory");
>> + } else {
>> + debugf4("%s: csrow/channel to increment: (%d,%d)\n",
>> + __func__, row, chan);
>> + if (p == label)
>> + strcpy(label, "unknown memory");
>> + if (type == HW_EVENT_ERR_CORRECTED) {
>> + if (row >= 0) {
>> + mci->csrows[row].ce_count++;
>> + if (chan >= 0)
>> + mci->csrows[row].channels[chan].ce_count++;
>> + }
>> + } else
>> + if (row >= 0)
>> + mci->csrows[row].ue_count++;
>> }
>>
>> - if (channelb >= mci->csrows[csrow].nr_channels) {
>> - /* something is wrong */
>> - edac_mc_printk(mci, KERN_ERR,
>> - "INTERNAL ERROR: channel-b out of range "
>> - "(%d >= %d)\n",
>> - channelb, mci->csrows[csrow].nr_channels);
>> - edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR");
>> - return;
>> + /* Fill the RAM location data */
>> + p = location;
>> + for (i = 0; i < mci->n_layers; i++) {
>> + if (pos[i] < 0)
>> + continue;
>
> newline.
>
>> + p += sprintf(p, "%s %d ",
>> + edac_layer_name[mci->layers[i].type],
>> + pos[i]);
>> }
>>
>> - mci->ue_count++;
>> - mci->csrows[csrow].ue_count++;
>> -
>> - /* Generate the DIMM labels from the specified channels */
>> - label = mci->csrows[csrow].channels[channela].dimm->label;
>> - chars = snprintf(pos, len + 1, "%s", label);
>> - len -= chars;
>> - pos += chars;
>> -
>> - chars = snprintf(pos, len + 1, "-%s",
>> - mci->csrows[csrow].channels[channelb].dimm->label);
>> -
>> - if (edac_mc_get_log_ue())
>> - edac_mc_printk(mci, KERN_EMERG,
>> - "UE row %d, channel-a= %d channel-b= %d "
>> - "labels \"%s\": %s\n", csrow, channela, channelb,
>> - labels, msg);
>> -
>> - if (edac_mc_get_panic_on_ue())
>> - panic("UE row %d, channel-a= %d channel-b= %d "
>> - "labels \"%s\": %s\n", csrow, channela,
>> - channelb, labels, msg);
>> -}
>> -EXPORT_SYMBOL(edac_mc_handle_fbd_ue);
>> + /* Memory type dependent details about the error */
>> + if (type == HW_EVENT_ERR_CORRECTED)
>> + snprintf(detail, sizeof(detail),
>> + "page 0x%lx offset 0x%lx grain %d syndrome 0x%lx",
>> + page_frame_number, offset_in_page,
>> + grain, syndrome);
>> + else
>> + snprintf(detail, sizeof(detail),
>> + "page 0x%lx offset 0x%lx grain %d",
>> + page_frame_number, offset_in_page, grain);
>> +
>> + if (type == HW_EVENT_ERR_CORRECTED) {
>> + if (edac_mc_get_log_ce())
>> + edac_mc_printk(mci, KERN_WARNING,
>> + "CE %s on %s (%s%s %s)\n",
>> + msg, label, location,
>> + detail, other_detail);
>
> two back-to-back if-statements with the same conditional, pls merge
> them. Better yet, this edac_mc_handle_error() is huuge, pls split its
> functionality into well-abstracted helpers, for example one which deals
> with HW_EVENT_ERR_CORRECTED, another with HW_EVENT_ERR_UNCORRECTED, etc.
>
>> + edac_increment_ce_error(mci, enable_filter, pos);
>> +
>> + if (mci->scrub_mode & SCRUB_SW_SRC) {
>> + /*
>> + * Some MC's can remap memory so that it is still
>
> memory controllers (called MCs below)
>
>> + * available at a different address when PCI devices
>> + * map into memory.
>> + * MC's that can't do this lose the memory where PCI
>
> this, lose...
>
>> + * devices are mapped. This mapping is MC dependent
>
> MC-dependent
Fixed all above.
>
>> + * and so we call back into the MC driver for it to
>> + * map the MC page to a physical (CPU) page which can
>> + * then be mapped to a virtual page - which can then
>> + * be scrubbed.
>> + */
>> + remapped_page = mci->ctl_page_to_phys ?
>> + mci->ctl_page_to_phys(mci, page_frame_number) :
>> + page_frame_number;
>> +
>> + edac_mc_scrub_block(remapped_page,
>> + offset_in_page, grain);
>> + }
>
> The SCRUB_SW_SRC piece can be another function.
It is now part of the edac_ce_error().
>
>> + } else {
>> + if (edac_mc_get_log_ue())
>> + edac_mc_printk(mci, KERN_WARNING,
>> + "UE %s on %s (%s%s %s)\n",
>> + msg, label, location, detail, other_detail);
>>
>> -/*************************************************************
>> - * On Fully Buffered DIMM modules, this help function is
>> - * called to process CE events
>> - */
>> -void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci,
>> - unsigned int csrow, unsigned int channel, char *msg)
>> -{
>> - char *label = NULL;
>> + if (edac_mc_get_panic_on_ue())
>> + panic("UE %s on %s (%s%s %s)\n",
>> + msg, label, location, detail, other_detail);
>>
>> - /* Ensure boundary values */
>> - if (csrow >= mci->nr_csrows) {
>> - /* something is wrong */
>> - edac_mc_printk(mci, KERN_ERR,
>> - "INTERNAL ERROR: row out of range (%d >= %d)\n",
>> - csrow, mci->nr_csrows);
>> - edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR");
>> - return;
>> + edac_increment_ue_error(mci, enable_filter, pos);
>> }
>> - if (channel >= mci->csrows[csrow].nr_channels) {
>> - /* something is wrong */
>> - edac_mc_printk(mci, KERN_ERR,
>> - "INTERNAL ERROR: channel out of range (%d >= %d)\n",
>> - channel, mci->csrows[csrow].nr_channels);
>> - edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR");
>> - return;
>> - }
>> -
>> - label = mci->csrows[csrow].channels[channel].dimm->label;
>> -
>> - if (edac_mc_get_log_ce())
>> - /* FIXME - put in DIMM location */
>> - edac_mc_printk(mci, KERN_WARNING,
>> - "CE row %d, channel %d, label \"%s\": %s\n",
>> - csrow, channel, label, msg);
>> -
>> - mci->ce_count++;
>> - mci->csrows[csrow].ce_count++;
>> - mci->csrows[csrow].channels[channel].dimm->ce_count++;
>> - mci->csrows[csrow].channels[channel].ce_count++;
>> }
>> -EXPORT_SYMBOL(edac_mc_handle_fbd_ce);
>> +EXPORT_SYMBOL_GPL(edac_mc_handle_error);
>> diff --git a/include/linux/edac.h b/include/linux/edac.h
>> index 3b8798d909da..2b66109bc301 100644
>> --- a/include/linux/edac.h
>> +++ b/include/linux/edac.h
>> @@ -412,18 +412,20 @@ struct edac_mc_layer {
>> /* FIXME: add the proper per-location error counts */
>> struct dimm_info {
>> char label[EDAC_MC_LABEL_LEN + 1]; /* DIMM label on motherboard */
>> - unsigned memory_controller;
>> - unsigned csrow;
>> - unsigned csrow_channel;
>> +
>> + /* Memory location data */
>> + unsigned location[EDAC_MAX_LAYERS];
>> +
>> + struct mem_ctl_info *mci; /* the parent */
>>
>> u32 grain; /* granularity of reported error in bytes */
>> enum dev_type dtype; /* memory device type */
>> enum mem_type mtype; /* memory dimm type */
>> enum edac_type edac_mode; /* EDAC mode for this dimm */
>>
>> - u32 nr_pages; /* number of pages in csrow */
>> + u32 nr_pages; /* number of pages on this dimm */
>>
>> - u32 ce_count; /* Correctable Errors for this dimm */
>> + unsigned csrow, cschannel; /* Points to the old API data */
>> };
>>
>> /**
>> @@ -443,9 +445,10 @@ struct dimm_info {
>> */
>> struct rank_info {
>> int chan_idx;
>> - u32 ce_count;
>> struct csrow_info *csrow;
>> struct dimm_info *dimm;
>> +
>> + u32 ce_count; /* Correctable Errors for this csrow */
>> };
>>
>> struct csrow_info {
>> @@ -497,6 +500,11 @@ struct mcidev_sysfs_attribute {
>> ssize_t (*store)(struct mem_ctl_info *, const char *,size_t);
>> };
>>
>> +struct edac_hierarchy {
>> + char *name;
>> + unsigned nr;
>> +};
>
> What is that, unused leftovers?
Likely. I'll drop from this patch, readding it latter if it is still needed.
>
>> +
>> /* MEMORY controller information structure
>> */
>> struct mem_ctl_info {
>> @@ -541,13 +549,18 @@ struct mem_ctl_info {
>> unsigned long (*ctl_page_to_phys) (struct mem_ctl_info * mci,
>> unsigned long page);
>> int mc_idx;
>> - int nr_csrows;
>> struct csrow_info *csrows;
>> + unsigned nr_csrows, num_cschannel;
>> +
>> + /* Memory Controller hierarchy */
>> + unsigned n_layers;
>> + struct edac_mc_layer *layers;
>> + bool mem_is_per_rank;
>>
>> /*
>> * DIMM info. Will eventually remove the entire csrows_info some day
>> */
>> - unsigned nr_dimms;
>> + unsigned tot_dimms;
>> struct dimm_info *dimms;
>>
>> /*
>> @@ -562,12 +575,15 @@ struct mem_ctl_info {
>> const char *dev_name;
>> char proc_name[MC_PROC_NAME_MAX_LEN + 1];
>> void *pvt_info;
>> - u32 ue_noinfo_count; /* Uncorrectable Errors w/o info */
>> - u32 ce_noinfo_count; /* Correctable Errors w/o info */
>> - u32 ue_count; /* Total Uncorrectable Errors for this MC */
>> - u32 ce_count; /* Total Correctable Errors for this MC */
>> + u32 ue_count; /* Total Uncorrectable Errors for this MC */
>> + u32 ce_count; /* Total Correctable Errors for this MC */a
>
> Why are you touching ue_count and ce_count, I don't see any differences
> here?
This is also due to some rebase. Moved the error counters to be together with
the other ones.
>> unsigned long start_time; /* mci load start time (in jiffies) */
>>
>> + /* drivers shouldn't access this struct directly */
>
> Which struct, I only see unsigneds?
I mean those fields. Some drivers were touching at them directly in the past.
>
>> + unsigned ce_noinfo_count, ue_noinfo_count;
>> + unsigned ce_mc, ue_mc;
>
> What are those counters?
In the past, I was keeping ce_count/ue_count for the old API, and ce_mc/ue_mc
for the new API. This got merged on rebase v13 or v14.
Got rid of those.
>
>> + u32 *ce_per_layer[EDAC_MAX_LAYERS], *ue_per_layer[EDAC_MAX_LAYERS];
>> +
>> struct completion complete;
>>
>> /* edac sysfs device control */
>> @@ -580,7 +596,7 @@ struct mem_ctl_info {
>> * by the low level driver.
>> *
>> * Set by the low level driver to provide attributes at the
>> - * controller level, same level as 'ue_count' and 'ce_count' above.
>> + * controller level.
>> * An array of structures, NULL terminated
>> *
>> * If attributes are desired, then set to array of attributes
>
The following patch addresses the pointed issues. I've updated them
on my experimental branch at infradead:
git://git.infradead.org/users/mchehab/edac.git experimental
They'll also be soon available at:
git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-edac.git hw_events_v18
Regards,
Mauro
-
edac: Change internal representation to work with layers
From: Mauro Carvalho Chehab <mchehab@redhat.com>
Change the EDAC internal representation to work with non-csrow
based memory controllers.
There are lots of those memory controllers nowadays, and more
are coming. So, the EDAC internal representation needs to be
changed, in order to work with those memory controllers, while
preserving backward compatibility with the old ones.
The edac core was written with the idea that memory controllers
are able to directly access csrows.
This is not true for FB-DIMM and RAMBUS memory controllers.
Also, some recent advanced memory controllers don't present a per-csrows
view. Instead, they view memories as DIMMs, instead of ranks.
So, change the allocation and error report routines to allow
them to work with all types of architectures.
This will allow the removal of several hacks with FB-DIMM and RAMBUS
memory controllers.
Also, several tests were done on different platforms using different
x86 drivers.
TODO: a multi-rank DIMMs are currently represented by multiple DIMM
entries in struct dimm_info. That means that changing a label for one
rank won't change the same label for the other ranks at the same DIMM.
This bug is present since the beginning of the EDAC, so it is not a big
deal. However, on several drivers, it is possible to fix this issue, but
it should be a per-driver fix, as the csrow => DIMM arrangement may not
be equal for all. So, don't try to fix it here yet.
I tried to make this patch as short as possible, preceding it with
several other patches that simplified the logic here. Yet, as the
internal API changes, all drivers need changes. The changes are
generally bigger in the drivers for FB-DIMMs.
Cc: Aristeu Rozanski <arozansk@redhat.com>
Cc: Doug Thompson <norsk5@yahoo.com>
Cc: Borislav Petkov <borislav.petkov@amd.com>
Cc: Mark Gross <mark.gross@intel.com>
Cc: Jason Uhlenkott <juhlenko@akamai.com>
Cc: Tim Small <tim@buttersideup.com>
Cc: Ranganathan Desikan <ravi@jetztechnologies.com>
Cc: "Arvind R." <arvino55@gmail.com>
Cc: Olof Johansson <olof@lixom.net>
Cc: Egor Martovetsky <egor@pasemi.com>
Cc: Chris Metcalf <cmetcalf@tilera.com>
Cc: Michal Marek <mmarek@suse.cz>
Cc: Jiri Kosina <jkosina@suse.cz>
Cc: Joe Perches <joe@perches.com>
Cc: Dmitry Eremin-Solenikov <dbaryshkov@gmail.com>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Cc: Hitoshi Mitake <h.mitake@gmail.com>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: "Niklas Söderlund" <niklas.soderlund@ericsson.com>
Cc: Shaohui Xie <Shaohui.Xie@freescale.com>
Cc: Josh Boyer <jwboyer@gmail.com>
Cc: linuxppc-dev@lists.ozlabs.org
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
---
v18: Addresses the pointed issues on v17 review
diff --git a/drivers/edac/edac_core.h b/drivers/edac/edac_core.h
index e48ab31..1286c5e 100644
--- a/drivers/edac/edac_core.h
+++ b/drivers/edac/edac_core.h
@@ -447,8 +447,12 @@ static inline void pci_write_bits32(struct pci_dev *pdev, int offset,
#endif /* CONFIG_PCI */
-extern struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
- unsigned nr_chans, int edac_index);
+struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
+ unsigned nr_chans, int edac_index);
+struct mem_ctl_info *new_edac_mc_alloc(unsigned edac_index,
+ unsigned n_layers,
+ struct edac_mc_layer *layers,
+ unsigned sz_pvt);
extern int edac_mc_add_mc(struct mem_ctl_info *mci);
extern void edac_mc_free(struct mem_ctl_info *mci);
extern struct mem_ctl_info *edac_mc_find(int idx);
@@ -467,24 +471,78 @@ extern int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci,
* reporting logic and function interface - reduces conditional
* statement clutter and extra function arguments.
*/
-extern void edac_mc_handle_ce(struct mem_ctl_info *mci,
- unsigned long page_frame_number,
- unsigned long offset_in_page,
- unsigned long syndrome, int row, int channel,
- const char *msg);
-extern void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci,
- const char *msg);
-extern void edac_mc_handle_ue(struct mem_ctl_info *mci,
- unsigned long page_frame_number,
- unsigned long offset_in_page, int row,
- const char *msg);
-extern void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci,
- const char *msg);
-extern void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci, unsigned int csrow,
- unsigned int channel0, unsigned int channel1,
- char *msg);
-extern void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci, unsigned int csrow,
- unsigned int channel, char *msg);
+
+void edac_mc_handle_error(const enum hw_event_mc_err_type type,
+ struct mem_ctl_info *mci,
+ const unsigned long page_frame_number,
+ const unsigned long offset_in_page,
+ const unsigned long syndrome,
+ const int layer0,
+ const int layer1,
+ const int layer2,
+ const char *msg,
+ const char *other_detail,
+ const void *mcelog);
+
+static inline void edac_mc_handle_ce(struct mem_ctl_info *mci,
+ unsigned long page_frame_number,
+ unsigned long offset_in_page,
+ unsigned long syndrome, int row, int channel,
+ const char *msg)
+{
+ edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
+ page_frame_number, offset_in_page, syndrome,
+ row, channel, -1, msg, NULL, NULL);
+}
+
+static inline void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci,
+ const char *msg)
+{
+ edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
+ 0, 0, 0, -1, -1, -1, msg, NULL, NULL);
+}
+
+static inline void edac_mc_handle_ue(struct mem_ctl_info *mci,
+ unsigned long page_frame_number,
+ unsigned long offset_in_page, int row,
+ const char *msg)
+{
+ edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
+ page_frame_number, offset_in_page, 0,
+ row, -1, -1, msg, NULL, NULL);
+}
+
+static inline void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci,
+ const char *msg)
+{
+ edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
+ 0, 0, 0, -1, -1, -1, msg, NULL, NULL);
+}
+
+static inline void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci,
+ unsigned int csrow,
+ unsigned int channel0,
+ unsigned int channel1,
+ char *msg)
+{
+ /*
+ *FIXME: The error can also be at channel1 (e. g. at the second
+ * channel of the same branch). The fix is to push
+ * edac_mc_handle_error() call into each driver
+ */
+ edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci,
+ 0, 0, 0,
+ csrow, channel0, -1, msg, NULL, NULL);
+}
+
+static inline void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci,
+ unsigned int csrow,
+ unsigned int channel, char *msg)
+{
+ edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci,
+ 0, 0, 0,
+ csrow, channel, -1, msg, NULL, NULL);
+}
/*
* edac_device APIs
@@ -496,6 +554,7 @@ extern void edac_device_handle_ue(struct edac_device_ctl_info *edac_dev,
extern void edac_device_handle_ce(struct edac_device_ctl_info *edac_dev,
int inst_nr, int block_nr, const char *msg);
extern int edac_device_alloc_index(void);
+extern const char *edac_layer_name[];
/*
* edac_pci APIs
diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c
index 6ec967a..10cebb8 100644
--- a/drivers/edac/edac_mc.c
+++ b/drivers/edac/edac_mc.c
@@ -44,9 +44,25 @@ static void edac_mc_dump_channel(struct rank_info *chan)
debugf4("\tchannel = %p\n", chan);
debugf4("\tchannel->chan_idx = %d\n", chan->chan_idx);
debugf4("\tchannel->csrow = %p\n\n", chan->csrow);
- debugf4("\tdimm->ce_count = %d\n", chan->dimm->ce_count);
- debugf4("\tdimm->label = '%s'\n", chan->dimm->label);
- debugf4("\tdimm->nr_pages = 0x%x\n", chan->dimm->nr_pages);
+ debugf4("\tchannel->dimm = %p\n", chan->dimm);
+}
+
+static void edac_mc_dump_dimm(struct dimm_info *dimm)
+{
+ int i;
+
+ debugf4("\tdimm = %p\n", dimm);
+ debugf4("\tdimm->label = '%s'\n", dimm->label);
+ debugf4("\tdimm->nr_pages = 0x%x\n", dimm->nr_pages);
+ debugf4("\tdimm location ");
+ for (i = 0; i < dimm->mci->n_layers; i++) {
+ printk(KERN_CONT "%d", dimm->location[i]);
+ if (i < dimm->mci->n_layers - 1)
+ printk(KERN_CONT ".");
+ }
+ printk(KERN_CONT "\n");
+ debugf4("\tdimm->grain = %d\n", dimm->grain);
+ debugf4("\tdimm->nr_pages = 0x%x\n", dimm->nr_pages);
}
static void edac_mc_dump_csrow(struct csrow_info *csrow)
@@ -70,6 +86,8 @@ static void edac_mc_dump_mci(struct mem_ctl_info *mci)
debugf4("\tmci->edac_check = %p\n", mci->edac_check);
debugf3("\tmci->nr_csrows = %d, csrows = %p\n",
mci->nr_csrows, mci->csrows);
+ debugf3("\tmci->nr_dimms = %d, dimms = %p\n",
+ mci->tot_dimms, mci->dimms);
debugf3("\tdev = %p\n", mci->dev);
debugf3("\tmod_name:ctl_name = %s:%s\n", mci->mod_name, mci->ctl_name);
debugf3("\tpvt_info = %p\n\n", mci->pvt_info);
@@ -157,10 +175,20 @@ void *edac_align_ptr(void **p, unsigned size, int n_elems)
}
/**
- * edac_mc_alloc: Allocate a struct mem_ctl_info structure
- * @size_pvt: size of private storage needed
- * @nr_csrows: Number of CWROWS needed for this MC
- * @nr_chans: Number of channels for the MC
+ * edac_mc_alloc: Allocate and partially fill a struct mem_ctl_info structure
+ * @mc_num: Memory controller number
+ * @n_layers: Number of MC hierarchy layers
+ * layers: Describes each layer as seen by the Memory Controller
+ * @size_pvt: size of private storage needed
+ *
+ *
+ * Non-csrow based drivers (like FB-DIMM and RAMBUS ones) will likely report
+ * such DIMMS properly, but the CSROWS-based ones will likely do the wrong
+ * thing, as two chip select values are used for dual-rank memories (and 4, for
+ * quad-rank ones). I suspect that this issue could be solved inside the EDAC
+ * core for SDRAM memories, but it requires further study at JEDEC JESD 21C.
+ *
+ * In summary, solving this issue is not easy, as it requires a lot of testing.
*
* Everything is kmalloc'ed as one big chunk - more efficient.
* Only can be used if all structures have the same lifetime - otherwise
@@ -168,22 +196,49 @@ void *edac_align_ptr(void **p, unsigned size, int n_elems)
*
* Use edac_mc_free() to free mc structures allocated by this function.
*
+ * NOTE: drivers handle multi-rank memories in different ways: in some
+ * drivers, one multi-rank memory stick is mapped as one entry, while, in
+ * others, a single multi-rank memory stick would be mapped into several
+ * entries. Currently, this function will allocate multiple struct dimm_info
+ * on such scenarios, as grouping the multiple ranks require drivers change.
+ *
* Returns:
* NULL allocation failed
* struct mem_ctl_info pointer
*/
-struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
- unsigned nr_chans, int edac_index)
+struct mem_ctl_info *new_edac_mc_alloc(unsigned mc_num,
+ unsigned n_layers,
+ struct edac_mc_layer *layers,
+ unsigned sz_pvt)
{
- void *ptr = NULL;
struct mem_ctl_info *mci;
- struct csrow_info *csi, *csrow;
+ struct edac_mc_layer *layer;
+ struct csrow_info *csi, *csr;
struct rank_info *chi, *chp, *chan;
struct dimm_info *dimm;
- void *pvt;
- unsigned size;
- int row, chn;
- int err;
+ u32 *ce_per_layer[EDAC_MAX_LAYERS], *ue_per_layer[EDAC_MAX_LAYERS];
+ unsigned pos[EDAC_MAX_LAYERS];
+ void *pvt, *ptr = NULL;
+ unsigned size, tot_dimms = 1, count = 1;
+ unsigned tot_csrows = 1, tot_channels = 1, tot_errcount = 0;
+ int i, j, err, row, chn;
+ bool per_rank = false;
+
+ BUG_ON(n_layers > EDAC_MAX_LAYERS || n_layers == 0);
+ /*
+ * Calculate the total amount of dimms and csrows/cschannels while
+ * in the old API emulation mode
+ */
+ for (i = 0; i < n_layers; i++) {
+ tot_dimms *= layers[i].size;
+ if (layers[i].is_virt_csrow)
+ tot_csrows *= layers[i].size;
+ else
+ tot_channels *= layers[i].size;
+
+ if (layers[i].type == EDAC_MC_LAYER_CHIP_SELECT)
+ per_rank = true;
+ }
/* Figure out the offsets of the various items from the start of an mc
* structure. We want the alignment of each item to be at least as
@@ -191,12 +246,27 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
* hardcode everything into a single struct.
*/
mci = edac_align_ptr(&ptr, sizeof(*mci), 1);
- csi = edac_align_ptr(&ptr, sizeof(*csi), nr_csrows);
- chi = edac_align_ptr(&ptr, sizeof(*chi), nr_csrows * nr_chans);
- dimm = edac_align_ptr(&ptr, sizeof(*dimm), nr_csrows * nr_chans);
+ layer = edac_align_ptr(&ptr, sizeof(*layer), n_layers);
+ csi = edac_align_ptr(&ptr, sizeof(*csi), tot_csrows);
+ chi = edac_align_ptr(&ptr, sizeof(*chi), tot_csrows * tot_channels);
+ dimm = edac_align_ptr(&ptr, sizeof(*dimm), tot_dimms);
+ for (i = 0; i < n_layers; i++) {
+ count *= layers[i].size;
+ debugf4("%s: errcount layer %d size %d\n", __func__, i, count);
+ ce_per_layer[i] = edac_align_ptr(&ptr, sizeof(u32), count);
+ ue_per_layer[i] = edac_align_ptr(&ptr, sizeof(u32), count);
+ tot_errcount += 2 * count;
+ }
+
+ debugf4("%s: allocating %d error counters\n", __func__, tot_errcount);
pvt = edac_align_ptr(&ptr, sz_pvt, 1);
size = ((unsigned long)pvt) + sz_pvt;
+ debugf1("%s(): allocating %u bytes for mci data (%d %s, %d csrows/channels)\n",
+ __func__, size,
+ tot_dimms,
+ per_rank ? "ranks" : "dimms",
+ tot_csrows * tot_channels);
mci = kzalloc(size, GFP_KERNEL);
if (mci == NULL)
return NULL;
@@ -204,42 +274,90 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
/* Adjust pointers so they point within the memory we just allocated
* rather than an imaginary chunk of memory located at address 0.
*/
+ layer = (struct edac_mc_layer *)(((char *)mci) + ((unsigned long)layer));
csi = (struct csrow_info *)(((char *)mci) + ((unsigned long)csi));
chi = (struct rank_info *)(((char *)mci) + ((unsigned long)chi));
dimm = (struct dimm_info *)(((char *)mci) + ((unsigned long)dimm));
+ for (i = 0; i < n_layers; i++) {
+ mci->ce_per_layer[i] = (u32 *)((char *)mci + ((unsigned long)ce_per_layer[i]));
+ mci->ue_per_layer[i] = (u32 *)((char *)mci + ((unsigned long)ue_per_layer[i]));
+ }
pvt = sz_pvt ? (((char *)mci) + ((unsigned long)pvt)) : NULL;
/* setup index and various internal pointers */
- mci->mc_idx = edac_index;
+ mci->mc_idx = mc_num;
mci->csrows = csi;
mci->dimms = dimm;
+ mci->tot_dimms = tot_dimms;
mci->pvt_info = pvt;
- mci->nr_csrows = nr_csrows;
+ mci->n_layers = n_layers;
+ mci->layers = layer;
+ memcpy(mci->layers, layers, sizeof(*layer) * n_layers);
+ mci->nr_csrows = tot_csrows;
+ mci->num_cschannel = tot_channels;
+ mci->mem_is_per_rank = per_rank;
/*
- * For now, assumes that a per-csrow arrangement for dimms.
- * This will be latter changed.
+ * Fill the csrow struct
*/
- dimm = mci->dimms;
-
- for (row = 0; row < nr_csrows; row++) {
- csrow = &csi[row];
- csrow->csrow_idx = row;
- csrow->mci = mci;
- csrow->nr_channels = nr_chans;
- chp = &chi[row * nr_chans];
- csrow->channels = chp;
-
- for (chn = 0; chn < nr_chans; chn++) {
+ for (row = 0; row < tot_csrows; row++) {
+ csr = &csi[row];
+ csr->csrow_idx = row;
+ csr->mci = mci;
+ csr->nr_channels = tot_channels;
+ chp = &chi[row * tot_channels];
+ csr->channels = chp;
+
+ for (chn = 0; chn < tot_channels; chn++) {
chan = &chp[chn];
chan->chan_idx = chn;
- chan->csrow = csrow;
+ chan->csrow = csr;
+ }
+ }
- mci->csrows[row].channels[chn].dimm = dimm;
- dimm->csrow = row;
- dimm->csrow_channel = chn;
- dimm++;
- mci->nr_dimms++;
+ /*
+ * Fill the dimm struct
+ */
+ memset(&pos, 0, sizeof(pos));
+ row = 0;
+ chn = 0;
+ debugf4("%s: initializing %d %s\n", __func__, tot_dimms,
+ per_rank ? "ranks" : "dimms");
+ for (i = 0; i < tot_dimms; i++) {
+ chan = &csi[row].channels[chn];
+ dimm = EDAC_DIMM_PTR(layer, mci->dimms, n_layers,
+ pos[0], pos[1], pos[2]);
+ dimm->mci = mci;
+
+ debugf2("%s: %d: %s%zd (%d:%d:%d): row %d, chan %d\n", __func__,
+ i, per_rank ? "rank" : "dimm", (dimm - mci->dimms),
+ pos[0], pos[1], pos[2], row, chn);
+
+ /* Copy DIMM location */
+ for (j = 0; j < n_layers; j++)
+ dimm->location[j] = pos[j];
+
+ /* Link it to the csrows old API data */
+ chan->dimm = dimm;
+ dimm->csrow = row;
+ dimm->cschannel = chn;
+
+ /* Increment csrow location */
+ for (j = n_layers - 1; j >= 0; j--)
+ if (layers[j].is_virt_csrow)
+ break;
+ row++;
+ if (row == tot_csrows) {
+ row = 0;
+ chn++;
+ }
+
+ /* Increment dimm location */
+ for (j = n_layers - 1; j >= 0; j--) {
+ pos[j]++;
+ if (pos[j] < layers[j].size)
+ break;
+ pos[j] = 0;
}
}
@@ -263,6 +381,46 @@ struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
*/
return mci;
}
+EXPORT_SYMBOL_GPL(new_edac_mc_alloc);
+
+/**
+ * edac_mc_alloc: Allocate and partially fill a struct mem_ctl_info structure
+ * @mc_num: Memory controller number
+ * @n_layers: Number of layers at the MC hierarchy
+ * layers: Describes each layer as seen by the Memory Controller
+ * @size_pvt: Size of private storage needed
+ *
+ *
+ * FIXME: drivers handle multi-rank memories in different ways: some
+ * drivers map multi-ranked DIMMs as one DIMM while others
+ * as several DIMMs.
+ *
+ * Everything is kmalloc'ed as one big chunk - more efficient.
+ * It can only be used if all structures have the same lifetime - otherwise
+ * you have to allocate and initialize your own structures.
+ *
+ * Use edac_mc_free() to free mc structures allocated by this function.
+ *
+ * Returns:
+ * On failure: NULL
+ * On success: struct mem_ctl_info pointer
+ */
+
+struct mem_ctl_info *edac_mc_alloc(unsigned sz_pvt, unsigned nr_csrows,
+ unsigned nr_chans, int mc_num)
+{
+ unsigned n_layers = 2;
+ struct edac_mc_layer layers[n_layers];
+
+ layers[0].type = EDAC_MC_LAYER_CHIP_SELECT;
+ layers[0].size = nr_csrows;
+ layers[0].is_virt_csrow = true;
+ layers[1].type = EDAC_MC_LAYER_CHANNEL;
+ layers[1].size = nr_chans;
+ layers[1].is_virt_csrow = false;
+
+ return new_edac_mc_alloc(mc_num, ARRAY_SIZE(layers), layers, sz_pvt);
+}
EXPORT_SYMBOL_GPL(edac_mc_alloc);
/**
@@ -528,7 +686,6 @@ EXPORT_SYMBOL(edac_mc_find);
* edac_mc_add_mc: Insert the 'mci' structure into the mci global list and
* create sysfs entries associated with mci structure
* @mci: pointer to the mci structure to be added to the list
- * @mc_idx: A unique numeric identifier to be assigned to the 'mci' structure.
*
* Return:
* 0 Success
@@ -555,6 +712,8 @@ int edac_mc_add_mc(struct mem_ctl_info *mci)
edac_mc_dump_channel(&mci->csrows[i].
channels[j]);
}
+ for (i = 0; i < mci->tot_dimms; i++)
+ edac_mc_dump_dimm(&mci->dimms[i]);
}
#endif
mutex_lock(&mem_ctls_mutex);
@@ -712,261 +871,289 @@ int edac_mc_find_csrow_by_page(struct mem_ctl_info *mci, unsigned long page)
}
EXPORT_SYMBOL_GPL(edac_mc_find_csrow_by_page);
-/* FIXME - setable log (warning/emerg) levels */
-/* FIXME - integrate with evlog: http://evlog.sourceforge.net/ */
-void edac_mc_handle_ce(struct mem_ctl_info *mci,
- unsigned long page_frame_number,
- unsigned long offset_in_page, unsigned long syndrome,
- int row, int channel, const char *msg)
+const char *edac_layer_name[] = {
+ [EDAC_MC_LAYER_BRANCH] = "branch",
+ [EDAC_MC_LAYER_CHANNEL] = "channel",
+ [EDAC_MC_LAYER_SLOT] = "slot",
+ [EDAC_MC_LAYER_CHIP_SELECT] = "csrow",
+};
+EXPORT_SYMBOL_GPL(edac_layer_name);
+
+static void edac_inc_ce_error(struct mem_ctl_info *mci,
+ bool enable_per_layer_report,
+ const int pos[EDAC_MAX_LAYERS])
{
- unsigned long remapped_page;
- char *label = NULL;
- u32 grain;
+ int i, index = 0;
- debugf3("MC%d: %s()\n", mci->mc_idx, __func__);
+ mci->ce_count++;
- /* FIXME - maybe make panic on INTERNAL ERROR an option */
- if (row >= mci->nr_csrows || row < 0) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: row out of range "
- "(%d >= %d)\n", row, mci->nr_csrows);
- edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR");
+ if (!enable_per_layer_report) {
+ mci->ce_noinfo_count++;
return;
}
- if (channel >= mci->csrows[row].nr_channels || channel < 0) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: channel out of range "
- "(%d >= %d)\n", channel,
- mci->csrows[row].nr_channels);
- edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR");
+ for (i = 0; i < mci->n_layers; i++) {
+ if (pos[i] < 0)
+ break;
+ index += pos[i];
+ mci->ce_per_layer[i][index]++;
+
+ if (i < mci->n_layers - 1)
+ index *= mci->layers[i + 1].size;
+ }
+}
+
+static void edac_inc_ue_error(struct mem_ctl_info *mci,
+ bool enable_per_layer_report,
+ const int pos[EDAC_MAX_LAYERS])
+{
+ int i, index = 0;
+
+ mci->ue_count++;
+
+ if (!enable_per_layer_report) {
+ mci->ce_noinfo_count++;
return;
}
- label = mci->csrows[row].channels[channel].dimm->label;
- grain = mci->csrows[row].channels[channel].dimm->grain;
+ for (i = 0; i < mci->n_layers; i++) {
+ if (pos[i] < 0)
+ break;
+ index += pos[i];
+ mci->ue_per_layer[i][index]++;
+
+ if (i < mci->n_layers - 1)
+ index *= mci->layers[i + 1].size;
+ }
+}
+
+static void edac_ce_error(struct mem_ctl_info *mci,
+ const int pos[EDAC_MAX_LAYERS],
+ const char *msg,
+ const char *location,
+ const char *label,
+ const char *detail,
+ const char *other_detail,
+ const bool enable_per_layer_report,
+ const unsigned long page_frame_number,
+ const unsigned long offset_in_page,
+ u32 grain)
+{
+ unsigned long remapped_page;
if (edac_mc_get_log_ce())
- /* FIXME - put in DIMM location */
edac_mc_printk(mci, KERN_WARNING,
- "CE page 0x%lx, offset 0x%lx, grain %d, syndrome "
- "0x%lx, row %d, channel %d, label \"%s\": %s\n",
- page_frame_number, offset_in_page,
- grain, syndrome, row, channel,
- label, msg);
-
- mci->ce_count++;
- mci->csrows[row].ce_count++;
- mci->csrows[row].channels[channel].dimm->ce_count++;
- mci->csrows[row].channels[channel].ce_count++;
+ "CE %s on %s (%s%s %s)\n",
+ msg, label, location,
+ detail, other_detail);
+ edac_inc_ce_error(mci, enable_per_layer_report, pos);
if (mci->scrub_mode & SCRUB_SW_SRC) {
/*
- * Some MC's can remap memory so that it is still available
- * at a different address when PCI devices map into memory.
- * MC's that can't do this lose the memory where PCI devices
- * are mapped. This mapping is MC dependent and so we call
- * back into the MC driver for it to map the MC page to
- * a physical (CPU) page which can then be mapped to a virtual
- * page - which can then be scrubbed.
- */
+ * Some memory controllers (called MCs below) can remap
+ * memory so that it is still available at a different
+ * address when PCI devices map into memory.
+ * MC's that can't do this, lose the memory where PCI
+ * devices are mapped. This mapping is MC-dependent
+ * and so we call back into the MC driver for it to
+ * map the MC page to a physical (CPU) page which can
+ * then be mapped to a virtual page - which can then
+ * be scrubbed.
+ */
remapped_page = mci->ctl_page_to_phys ?
mci->ctl_page_to_phys(mci, page_frame_number) :
page_frame_number;
- edac_mc_scrub_block(remapped_page, offset_in_page, grain);
+ edac_mc_scrub_block(remapped_page,
+ offset_in_page, grain);
}
}
-EXPORT_SYMBOL_GPL(edac_mc_handle_ce);
-void edac_mc_handle_ce_no_info(struct mem_ctl_info *mci, const char *msg)
+static void edac_ue_error(struct mem_ctl_info *mci,
+ const int pos[EDAC_MAX_LAYERS],
+ const char *msg,
+ const char *location,
+ const char *label,
+ const char *detail,
+ const char *other_detail,
+ const bool enable_per_layer_report)
{
- if (edac_mc_get_log_ce())
+ if (edac_mc_get_log_ue())
edac_mc_printk(mci, KERN_WARNING,
- "CE - no information available: %s\n", msg);
+ "UE %s on %s (%s%s %s)\n",
+ msg, label, location, detail, other_detail);
- mci->ce_noinfo_count++;
- mci->ce_count++;
+ if (edac_mc_get_panic_on_ue())
+ panic("UE %s on %s (%s%s %s)\n",
+ msg, label, location, detail, other_detail);
+
+ edac_inc_ue_error(mci, enable_per_layer_report, pos);
}
-EXPORT_SYMBOL_GPL(edac_mc_handle_ce_no_info);
-void edac_mc_handle_ue(struct mem_ctl_info *mci,
- unsigned long page_frame_number,
- unsigned long offset_in_page, int row, const char *msg)
+#define OTHER_LABEL " or "
+void edac_mc_handle_error(const enum hw_event_mc_err_type type,
+ struct mem_ctl_info *mci,
+ const unsigned long page_frame_number,
+ const unsigned long offset_in_page,
+ const unsigned long syndrome,
+ const int layer0,
+ const int layer1,
+ const int layer2,
+ const char *msg,
+ const char *other_detail,
+ const void *mcelog)
{
- int len = EDAC_MC_LABEL_LEN * 4;
- char labels[len + 1];
- char *pos = labels;
- int chan;
- int chars;
- char *label = NULL;
+ /* FIXME: too much for stack: move it to some pre-alocated area */
+ char detail[80], location[80];
+ char label[(EDAC_MC_LABEL_LEN + 1 + sizeof(OTHER_LABEL)) * mci->tot_dimms];
+ char *p;
+ int row = -1, chan = -1;
+ int pos[EDAC_MAX_LAYERS] = { layer0, layer1, layer2 };
+ int i;
u32 grain;
+ bool enable_per_layer_report = false;
debugf3("MC%d: %s()\n", mci->mc_idx, __func__);
- /* FIXME - maybe make panic on INTERNAL ERROR an option */
- if (row >= mci->nr_csrows || row < 0) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: row out of range "
- "(%d >= %d)\n", row, mci->nr_csrows);
- edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR");
- return;
- }
-
- grain = mci->csrows[row].channels[0].dimm->grain;
- label = mci->csrows[row].channels[0].dimm->label;
- chars = snprintf(pos, len + 1, "%s", label);
- len -= chars;
- pos += chars;
-
- for (chan = 1; (chan < mci->csrows[row].nr_channels) && (len > 0);
- chan++) {
- label = mci->csrows[row].channels[chan].dimm->label;
- chars = snprintf(pos, len + 1, ":%s", label);
- len -= chars;
- pos += chars;
+ /*
+ * Check if the event report is consistent and if the memory
+ * location is known. If it is known, enable_per_layer_report will be
+ * true, the DIMM(s) label info will be filled and the per-layer
+ * error counters will be incremented.
+ */
+ for (i = 0; i < mci->n_layers; i++) {
+ if (pos[i] >= (int)mci->layers[i].size) {
+ if (type == HW_EVENT_ERR_CORRECTED)
+ p = "CE";
+ else
+ p = "UE";
+
+ edac_mc_printk(mci, KERN_ERR,
+ "INTERNAL ERROR: %s value is out of range (%d >= %d)\n",
+ edac_layer_name[mci->layers[i].type],
+ pos[i], mci->layers[i].size);
+ /*
+ * Instead of just returning it, let's use what's
+ * known about the error. The increment routines and
+ * the DIMM filter logic will do the right thing by
+ * pointing the likely damaged DIMMs.
+ */
+ pos[i] = -1;
+ }
+ if (pos[i] >= 0)
+ enable_per_layer_report = true;
}
- if (edac_mc_get_log_ue())
- edac_mc_printk(mci, KERN_EMERG,
- "UE page 0x%lx, offset 0x%lx, grain %d, row %d, "
- "labels \"%s\": %s\n", page_frame_number,
- offset_in_page, grain, row, labels, msg);
-
- if (edac_mc_get_panic_on_ue())
- panic("EDAC MC%d: UE page 0x%lx, offset 0x%lx, grain %d, "
- "row %d, labels \"%s\": %s\n", mci->mc_idx,
- page_frame_number, offset_in_page,
- grain, row, labels, msg);
-
- mci->ue_count++;
- mci->csrows[row].ue_count++;
-}
-EXPORT_SYMBOL_GPL(edac_mc_handle_ue);
-
-void edac_mc_handle_ue_no_info(struct mem_ctl_info *mci, const char *msg)
-{
- if (edac_mc_get_panic_on_ue())
- panic("EDAC MC%d: Uncorrected Error", mci->mc_idx);
+ /*
+ * Get the dimm label/grain that applies to the match criteria.
+ * As the error algorithm may not be able to point to just one memory
+ * stick, the logic here will get all possible labels that could
+ * pottentially be affected by the error.
+ * On FB-DIMM memory controllers, for uncorrected errors, it is common
+ * to have only the MC channel and the MC dimm (also called "branch")
+ * but the channel is not known, as the memory is arranged in pairs,
+ * where each memory belongs to a separate channel within the same
+ * branch.
+ */
+ grain = 0;
+ p = label;
+ *p = '\0';
+ for (i = 0; i < mci->tot_dimms; i++) {
+ struct dimm_info *dimm = &mci->dimms[i];
- if (edac_mc_get_log_ue())
- edac_mc_printk(mci, KERN_WARNING,
- "UE - no information available: %s\n", msg);
- mci->ue_noinfo_count++;
- mci->ue_count++;
-}
-EXPORT_SYMBOL_GPL(edac_mc_handle_ue_no_info);
+ if (layer0 >= 0 && layer0 != dimm->location[0])
+ continue;
+ if (layer1 >= 0 && layer1 != dimm->location[1])
+ continue;
+ if (layer2 >= 0 && layer2 != dimm->location[2])
+ continue;
-/*************************************************************
- * On Fully Buffered DIMM modules, this help function is
- * called to process UE events
- */
-void edac_mc_handle_fbd_ue(struct mem_ctl_info *mci,
- unsigned int csrow,
- unsigned int channela,
- unsigned int channelb, char *msg)
-{
- int len = EDAC_MC_LABEL_LEN * 4;
- char labels[len + 1];
- char *pos = labels;
- int chars;
- char *label;
-
- if (csrow >= mci->nr_csrows) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: row out of range (%d >= %d)\n",
- csrow, mci->nr_csrows);
- edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR");
- return;
- }
+ /* get the max grain, over the error match range */
+ if (dimm->grain > grain)
+ grain = dimm->grain;
- if (channela >= mci->csrows[csrow].nr_channels) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: channel-a out of range "
- "(%d >= %d)\n",
- channela, mci->csrows[csrow].nr_channels);
- edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR");
- return;
+ /*
+ * If the error is memory-controller wide, there's no need to
+ * seek for the affected DIMMs because the whole
+ * channel/memory controller/... may be affected.
+ * Also, don't show errors for empty DIMM slots.
+ */
+ if (enable_per_layer_report && dimm->nr_pages) {
+ if (p != label) {
+ strcpy(p, OTHER_LABEL);
+ p += strlen(OTHER_LABEL);
+ }
+ strcpy(p, dimm->label);
+ p += strlen(p);
+ *p = '\0';
+
+ /*
+ * get csrow/channel of the DIMM, in order to allow
+ * incrementing the compat API counters
+ */
+ debugf4("%s: %s csrows map: (%d,%d)\n",
+ __func__,
+ mci->mem_is_per_rank ? "rank" : "dimm",
+ dimm->csrow, dimm->cschannel);
+
+ if (row == -1)
+ row = dimm->csrow;
+ else if (row >= 0 && row != dimm->csrow)
+ row = -2;
+
+ if (chan == -1)
+ chan = dimm->cschannel;
+ else if (chan >= 0 && chan != dimm->cschannel)
+ chan = -2;
+ }
}
- if (channelb >= mci->csrows[csrow].nr_channels) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: channel-b out of range "
- "(%d >= %d)\n",
- channelb, mci->csrows[csrow].nr_channels);
- edac_mc_handle_ue_no_info(mci, "INTERNAL ERROR");
- return;
+ if (!enable_per_layer_report) {
+ strcpy(label, "any memory");
+ } else {
+ debugf4("%s: csrow/channel to increment: (%d,%d)\n",
+ __func__, row, chan);
+ if (p == label)
+ strcpy(label, "unknown memory");
+ if (type == HW_EVENT_ERR_CORRECTED) {
+ if (row >= 0) {
+ mci->csrows[row].ce_count++;
+ if (chan >= 0)
+ mci->csrows[row].channels[chan].ce_count++;
+ }
+ } else
+ if (row >= 0)
+ mci->csrows[row].ue_count++;
}
- mci->ue_count++;
- mci->csrows[csrow].ue_count++;
-
- /* Generate the DIMM labels from the specified channels */
- label = mci->csrows[csrow].channels[channela].dimm->label;
- chars = snprintf(pos, len + 1, "%s", label);
- len -= chars;
- pos += chars;
-
- chars = snprintf(pos, len + 1, "-%s",
- mci->csrows[csrow].channels[channelb].dimm->label);
-
- if (edac_mc_get_log_ue())
- edac_mc_printk(mci, KERN_EMERG,
- "UE row %d, channel-a= %d channel-b= %d "
- "labels \"%s\": %s\n", csrow, channela, channelb,
- labels, msg);
-
- if (edac_mc_get_panic_on_ue())
- panic("UE row %d, channel-a= %d channel-b= %d "
- "labels \"%s\": %s\n", csrow, channela,
- channelb, labels, msg);
-}
-EXPORT_SYMBOL(edac_mc_handle_fbd_ue);
-
-/*************************************************************
- * On Fully Buffered DIMM modules, this help function is
- * called to process CE events
- */
-void edac_mc_handle_fbd_ce(struct mem_ctl_info *mci,
- unsigned int csrow, unsigned int channel, char *msg)
-{
- char *label = NULL;
+ /* Fill the RAM location data */
+ p = location;
+ for (i = 0; i < mci->n_layers; i++) {
+ if (pos[i] < 0)
+ continue;
- /* Ensure boundary values */
- if (csrow >= mci->nr_csrows) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: row out of range (%d >= %d)\n",
- csrow, mci->nr_csrows);
- edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR");
- return;
- }
- if (channel >= mci->csrows[csrow].nr_channels) {
- /* something is wrong */
- edac_mc_printk(mci, KERN_ERR,
- "INTERNAL ERROR: channel out of range (%d >= %d)\n",
- channel, mci->csrows[csrow].nr_channels);
- edac_mc_handle_ce_no_info(mci, "INTERNAL ERROR");
- return;
+ p += sprintf(p, "%s %d ",
+ edac_layer_name[mci->layers[i].type],
+ pos[i]);
}
- label = mci->csrows[csrow].channels[channel].dimm->label;
+ /* Memory type dependent details about the error */
+ if (type == HW_EVENT_ERR_CORRECTED) {
+ snprintf(detail, sizeof(detail),
+ "page 0x%lx offset 0x%lx grain %d syndrome 0x%lx",
+ page_frame_number, offset_in_page,
+ grain, syndrome);
- if (edac_mc_get_log_ce())
- /* FIXME - put in DIMM location */
- edac_mc_printk(mci, KERN_WARNING,
- "CE row %d, channel %d, label \"%s\": %s\n",
- csrow, channel, label, msg);
+ edac_ce_error(mci, pos, msg, location, label, detail,
+ other_detail, enable_per_layer_report,
+ page_frame_number, offset_in_page, grain);
+ } else {
+ snprintf(detail, sizeof(detail),
+ "page 0x%lx offset 0x%lx grain %d",
+ page_frame_number, offset_in_page, grain);
- mci->ce_count++;
- mci->csrows[csrow].ce_count++;
- mci->csrows[csrow].channels[channel].dimm->ce_count++;
- mci->csrows[csrow].channels[channel].ce_count++;
+ edac_ue_error(mci, pos, msg, location, label, detail,
+ other_detail, enable_per_layer_report);
+ }
}
-EXPORT_SYMBOL(edac_mc_handle_fbd_ce);
+EXPORT_SYMBOL_GPL(edac_mc_handle_error);
diff --git a/include/linux/edac.h b/include/linux/edac.h
index 3b8798d..c8f507d 100644
--- a/include/linux/edac.h
+++ b/include/linux/edac.h
@@ -412,18 +412,20 @@ struct edac_mc_layer {
/* FIXME: add the proper per-location error counts */
struct dimm_info {
char label[EDAC_MC_LABEL_LEN + 1]; /* DIMM label on motherboard */
- unsigned memory_controller;
- unsigned csrow;
- unsigned csrow_channel;
+
+ /* Memory location data */
+ unsigned location[EDAC_MAX_LAYERS];
+
+ struct mem_ctl_info *mci; /* the parent */
u32 grain; /* granularity of reported error in bytes */
enum dev_type dtype; /* memory device type */
enum mem_type mtype; /* memory dimm type */
enum edac_type edac_mode; /* EDAC mode for this dimm */
- u32 nr_pages; /* number of pages in csrow */
+ u32 nr_pages; /* number of pages on this dimm */
- u32 ce_count; /* Correctable Errors for this dimm */
+ unsigned csrow, cschannel; /* Points to the old API data */
};
/**
@@ -443,9 +445,10 @@ struct dimm_info {
*/
struct rank_info {
int chan_idx;
- u32 ce_count;
struct csrow_info *csrow;
struct dimm_info *dimm;
+
+ u32 ce_count; /* Correctable Errors for this csrow */
};
struct csrow_info {
@@ -541,13 +544,18 @@ struct mem_ctl_info {
unsigned long (*ctl_page_to_phys) (struct mem_ctl_info * mci,
unsigned long page);
int mc_idx;
- int nr_csrows;
struct csrow_info *csrows;
+ unsigned nr_csrows, num_cschannel;
+
+ /* Memory Controller hierarchy */
+ unsigned n_layers;
+ struct edac_mc_layer *layers;
+ bool mem_is_per_rank;
/*
* DIMM info. Will eventually remove the entire csrows_info some day
*/
- unsigned nr_dimms;
+ unsigned tot_dimms;
struct dimm_info *dimms;
/*
@@ -562,12 +570,16 @@ struct mem_ctl_info {
const char *dev_name;
char proc_name[MC_PROC_NAME_MAX_LEN + 1];
void *pvt_info;
- u32 ue_noinfo_count; /* Uncorrectable Errors w/o info */
- u32 ce_noinfo_count; /* Correctable Errors w/o info */
- u32 ue_count; /* Total Uncorrectable Errors for this MC */
- u32 ce_count; /* Total Correctable Errors for this MC */
unsigned long start_time; /* mci load start time (in jiffies) */
+ /*
+ * drivers shouldn't access those fields directly, as the core
+ * already handles that.
+ */
+ u32 ce_noinfo_count, ue_noinfo_count;
+ u32 ue_count, ce_count;
+ u32 *ce_per_layer[EDAC_MAX_LAYERS], *ue_per_layer[EDAC_MAX_LAYERS];
+
struct completion complete;
/* edac sysfs device control */
@@ -580,7 +592,7 @@ struct mem_ctl_info {
* by the low level driver.
*
* Set by the low level driver to provide attributes at the
- * controller level, same level as 'ue_count' and 'ce_count' above.
+ * controller level.
* An array of structures, NULL terminated
*
* If attributes are desired, then set to array of attributes
^ permalink raw reply related
* Re: [PATCH v16 03/10]USB/ppc4xx: Add Synopsys DWC OTG Core Interface Layer (CIL)
From: Alan Cox @ 2012-05-03 12:40 UTC (permalink / raw)
To: Rupjyoti Sarmah; +Cc: linuxppc-dev, rsarmah, linux-kernel
In-Reply-To: <201205031228.q43CSw6k024808@amcc.com>
O> +void dwc_otg_flush_rx_fifo(struct core_if *core_if)
> +{
> + ulong global_regs = core_if->core_global_regs;
These are all a bit odd. The register has a given size so they ought to
be u32 or u64 etc as appropriate for the register in question, ditto the
cache in the structure.
> + for (i = 0; i < MAX_EPS_CHANNELS; i++) {
> + offset = i * DWC_EP_REG_OFFSET;
> +
> + dev_if->in_ep_regs[i] = (ulong)(reg_base +
> + DWC_DEV_IN_EP_REG_OFFSET +
> + offset);
And again some of the casting seems odd. If these are bus addresses they
should be typed as such.
> +static inline u32 dwc_reg_read(ulong reg , u32 offset)
> +{
> +
> +#ifdef CONFIG_DWC_OTG_REG_LE
> + return in_le32((void __iomem *)(reg + offset));
> +#else
> + return in_be32((void __iomem *)(reg + offset));
All this casting is a symptom of the same typing problems. They would all
go away if the types were right in the first place.
> + u32 global_regs = (u32) core_if->core_global_regs;
And again we keep finding these casts caused by wrong types
^ permalink raw reply
* [PATCH v16 01/10]USB/ppc4xx: Add Synopsys DWC OTG Register definitions
From: Rupjyoti Sarmah @ 2012-05-03 12:23 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel; +Cc: rsarmah
Add Synopsys Design Ware core register definitions.
Signed-off-by: Rupjyoti Sarmah <rsarmah@apm.com>
Signed-off-by: Tirumala R Marri <tmarri@apm.com>
Signed-off-by: Fushen Chen <fchen@apm.com>
Signed-off-by: Mark Miesfeld <mmiesfeld@apm.com>
---
drivers/usb/dwc/regs.h | 1326 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1326 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/dwc/regs.h
diff --git a/drivers/usb/dwc/regs.h b/drivers/usb/dwc/regs.h
new file mode 100644
index 0000000..c03252c
--- /dev/null
+++ b/drivers/usb/dwc/regs.h
@@ -0,0 +1,1326 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ *
+ * Revamped register difinitions by Tirumala R Marri(tmarri@apm.com)
+ * Updated by Rupjyoti Sarmah <rsarmah@apm.com>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __DWC_OTG_REGS_H__
+#define __DWC_OTG_REGS_H__
+
+#include <linux/types.h>
+/*Bit fields in the Device EP Transfer Size Register is 11 bits */
+#undef DWC_LIMITED_XFER_SIZE
+/*
+ * This file contains the Macro defintions for accessing the DWC_otg core
+ * registers.
+ *
+ * The application interfaces with the HS OTG core by reading from and
+ * writing to the Control and Status Register (CSR) space through the
+ * AHB Slave interface. These registers are 32 bits wide, and the
+ * addresses are 32-bit-block aligned.
+ * CSRs are classified as follows:
+ * - Core Global Registers
+ * - Device Mode Registers
+ * - Device Global Registers
+ * - Device Endpoint Specific Registers
+ * - Host Mode Registers
+ * - Host Global Registers
+ * - Host Port CSRs
+ * - Host Channel Specific Registers
+ *
+ * Only the Core Global registers can be accessed in both Device and
+ * Host modes. When the HS OTG core is operating in one mode, either
+ * Device or Host, the application must not access registers from the
+ * other mode. When the core switches from one mode to another, the
+ * registers in the new mode of operation must be reprogrammed as they
+ * would be after a power-on reset.
+ */
+
+/*
+ * DWC_otg Core registers. The core_global_regs structure defines the
+ * size and relative field offsets for the Core Global registers.
+ */
+#define DWC_GOTGCTL 0x000
+#define DWC_GOTGINT 0x004
+#define DWC_GAHBCFG 0x008
+#define DWC_GUSBCFG 0x00C
+#define DWC_GRSTCTL 0x010
+#define DWC_GINTSTS 0x014
+#define DWC_GINTMSK 0x018
+#define DWC_GRXSTSR 0x01C
+#define DWC_GRXSTSP 0x020
+#define DWC_GRXFSIZ 0x024
+#define DWC_GNPTXFSIZ 0x028
+#define DWC_GNPTXSTS 0x02C
+#define DWC_GI2CCTL 0x030
+#define DWC_VDCTL 0x034
+#define DWC_GGPIO 0x038
+#define DWC_GUID 0x03C
+#define DWC_GSNPSID 0x040
+#define DWC_GHWCFG1 0x044
+#define DWC_GHWCFG2 0x048
+#define DWC_GHWCFG3 0x04c
+#define DWC_GHWCFG4 0x050
+#define DWC_HPTXFSIZ 0x100
+#define DWC_DPTX_FSIZ_DIPTXF(x) (0x104 + x * 4) /* 15 <= x > 1 */
+
+#define DWC_GLBINTRMASK 0x0001
+#define DWC_DMAENABLE 0x0020
+#define DWC_NPTXEMPTYLVL_EMPTY 0x0080
+#define DWC_NPTXEMPTYLVL_HALFEMPTY 0x0000
+#define DWC_PTXEMPTYLVL_EMPTY 0x0100
+#define DWC_PTXEMPTYLVL_HALFEMPTY 0x0000
+
+#define DWC_SLAVE_ONLY_ARCH 0
+#define DWC_EXT_DMA_ARCH 1
+#define DWC_INT_DMA_ARCH 2
+
+#define DWC_MODE_HNP_SRP_CAPABLE 0
+#define DWC_MODE_SRP_ONLY_CAPABLE 1
+#define DWC_MODE_NO_HNP_SRP_CAPABLE 2
+#define DWC_MODE_SRP_CAPABLE_DEVICE 3
+#define DWC_MODE_NO_SRP_CAPABLE_DEVICE 4
+#define DWC_MODE_SRP_CAPABLE_HOST 5
+#define DWC_MODE_NO_SRP_CAPABLE_HOST 6
+
+/*
+ * These Macros represents the bit fields of the Core OTG Controland Status
+ * Register (GOTGCTL). Set the bits using the bit fields then write the u32
+ * value to the register.
+ */
+#define DWC_GCTL_BSESSION_VALID BIT(19)
+#define DWC_GCTL_CSESSION_VALID BIT(18)
+#define DWC_GCTL_DEBOUNCE BIT(17)
+#define DWC_GCTL_CONN_ID_STATUS BIT(16)
+#define DWC_GCTL_DEV_HNP_ENA BIT(11)
+#define DWC_GCTL_HOST_HNP_ENA BIT(10)
+#define DWC_GCTL_HNP_REQ BIT(9)
+#define DWC_GCTL_HOST_NEG_SUCCES BIT(8)
+#define DWC_GCTL_SES_REQ BIT(1)
+#define DWC_GCTL_SES_REQ_SUCCESS BIT(0)
+
+#define DWC_GCTL_BSESSION_VALID_RD(reg) (((reg) & (0x001 << 19)) >> 19)
+#define DWC_GCTL_CSESSION_VALID_RD(reg) (((reg) & (0x001 << 18)) >> 18)
+#define DWC_GCTL_DEBOUNCE_RD(reg) (((reg) & (0x001 << 17)) >> 17)
+#define DWC_GCTL_CONN_ID_STATUS_RD(reg) (((reg) & (0x001 << 16)) >> 16)
+#define DWC_GCTL_DEV_HNP_ENA_RD(reg) (((reg) & (0x001 << 11)) >> 11)
+#define DWC_GCTL_HOST_HNP_ENA_RD(reg) (((reg) & (0x001 << 10)) >> 10)
+#define DWC_GCTL_HNP_REQ_RD(reg) (((reg) & (0x001 << 9)) >> 9)
+#define DWC_GCTL_HOST_NEG_SUCCES_RD(reg) (((reg) & (0x001 << 8)) >> 8)
+#define DWC_GCTL_SES_REQ_RD(reg) (((reg) & (0x001 << 1)) >> 1)
+#define DWC_GCTL_SES_REQ_SUCCESS_RD(reg) (((reg) & (0x001 << 0)) >> 0)
+
+#define DWC_GCTL_BSESSION_VALID_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 19))) | ((x) << 19))
+#define DWC_GCTL_CSESSION_VALID_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 18))) | ((x) << 18))
+#define DWC_GCTL_DEBOUNCE_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 17))) | ((x) << 17))
+#define DWC_GCTL_CONN_ID_STATUS_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 16))) | ((x) << 16))
+#define DWC_GCTL_DEV_HNP_ENA_RW (reg, x) \
+ (((reg) & (~((u32)0x01 << 11))) | ((x) << 11))
+#define DWC_GCTL_HOST_HNP_ENA_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 10))) | ((x) << 10))
+#define DWC_GCTL_HNP_REQ_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 9))) | ((x) << 9))
+#define DWC_GCTL_HOST_NEG_SUCCES_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 8))) | ((x) << 8))
+#define DWC_GCTL_SES_REQ_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 1))) | ((x) << 1))
+#define DWC_GCTL_SES_REQ_SUCCESS_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 0))) | ((x) << 0))
+/*
+ * These Macros represents the bit fields of the Core OTG Interrupt Register
+ * (GOTGINT). Set/clear the bits using the bit fields then write the u32
+ * value to the register.
+ */
+#define DWC_GINT_DEBDONE BIT(19)
+#define DWC_GINT_DEVTOUT BIT(18)
+#define DWC_GINT_HST_NEGDET BIT(17)
+#define DWC_GINT_HST_NEGSUC BIT(9)
+#define DWC_GINT_SES_REQSUC BIT(8)
+#define DWC_GINT_SES_ENDDET BIT(2)
+
+/*
+ * These Macros represents the bit fields of the Core AHB Configuration Register
+ * (GAHBCFG). Set/clear the bits using the bit fields then write the u32 value
+ * to the register.
+ */
+#define DWC_AHBCFG_FIFO_EMPTY BIT(8)
+#define DWC_AHBCFG_NPFIFO_EMPTY BIT(7)
+#define DWC_AHBCFG_DMA_ENA BIT(5)
+#define DWC_AHBCFG_BURST_LEN(x) BIT(1)
+#define DWC_AHBCFG_GLBL_INT_MASK BIT(0)
+
+#define DWC_GAHBCFG_TXFEMPTYLVL_EMPTY 1
+#define DWC_GAHBCFG_TXFEMPTYLVL_HALFEMPTY 0
+#define DWC_GAHBCFG_DMAENABLE 1
+#define DWC_GAHBCFG_INT_DMA_BURST_SINGLE 0
+#define DWC_GAHBCFG_INT_DMA_BURST_INCR 1
+#define DWC_GAHBCFG_INT_DMA_BURST_INCR4 3
+#define DWC_GAHBCFG_INT_DMA_BURST_INCR8 5
+#define DWC_GAHBCFG_INT_DMA_BURST_INCR16 7
+
+/*
+
+ * (GUSBCFG). Set the bits using the bit fields then write the u32 value to the
+ * register.
+ */
+#define DWC_USBCFG_CORR_PKT BIT(31)
+#define DWC_USBCFG_FRC_DEV_MODE BIT(30)
+#define DWC_USBCFG_FRC_HST_MODE BIT(29)
+#define DWC_USBCFG_TERM_SEL_DL_PULSE BIT(22)
+#define DWC_USBCFG_ULPI_INTVBUS_INDICATOR BIT(21)
+#define DWC_USBCFG_ULPI_EXT_VBUS_DRV BIT(20)
+#define DWC_USBCFG_ULPI_CLK_SUS_M BIT(19)
+#define DWC_USBCFG_ULPI_AUTO_RES BIT(18)
+#define DWC_USBCFG_ULPI_FSLS BIT(17)
+#define DWC_USBCFG_OTGUTMIFSSEL BIT(16)
+#define DWC_USBCFG_PHYLPWRCLKSEL BIT(15)
+#define DWC_USBCFG_NPTXFRWNDEN BIT(14)
+#define DWC_USBCFG_TRN_TIME(x) (x << 10)
+#define DWC_USBCFG_HNP_CAP BIT(9)
+#define DWC_USBCFG_SRP_CAP BIT(8)
+#define DWC_USBCFG_DDRSEL BIT(7)
+#define DWC_USBCFG_USB_2_11 BIT(6)
+#define DWC_USBCFG_FSINTF BIT(5)
+#define DWC_USBCFG_ULPI_UTMI_SEL BIT(4)
+#define DWC_USBCFG_PHYIF BIT(3)
+#define DWC_USBCFG_TOUT_CAL(x) (x << 0)
+
+/*
+ * These Macros represents the bit fields of the Core Reset Register (GRSTCTL).
+ * Set/clear the bits using the bit fields then write the u32 value to the
+ * register.
+ */
+#define DWC_RSTCTL_AHB_IDLE BIT(31)
+#define DWC_RSTCTL_DMA_REQ BIT(30)
+#define DWC_RSTCTL_TX_FIFO_NUM(reg, x) \
+ (((reg) & (~((u32)0x1f << 6))) | ((x) << 6))
+#define DWC_RSTCTL_TX_FIFO_FLUSH BIT(5)
+#define DWC_RSTCTL_RX_FIFO_FLUSH BIT(4)
+#define DWC_RSTCTL_TKN_QUE_FLUSH BIT(3)
+#define DWC_RSTCTL_HSTFRM_CNTR_RST BIT(2)
+#define DWC_RSTCTL_HCLK_SFT_RST BIT(1)
+#define DWC_RSTCTL_SFT_RST BIT(1)
+#define DWC_GRSTCTL_TXFNUM_ALL 0x10
+
+/*
+ * These Macros represents the bit fields of the Core Interrupt Mask Register
+ * (GINTMSK). Set/clear the bits using the bit fields then write the u32 value
+ * to the register.
+ */
+#define DWC_INTMSK_WKP BIT(31)
+#define DWC_INTMSK_NEW_SES_DET BIT(30)
+#define DWC_INTMSK_SES_DISCON_DET BIT(29)
+#define DWC_INTMSK_CON_ID_STS_CHG BIT(28)
+#define DWC_INTMSK_P_TXFIFO_EMPTY BIT(26)
+#define DWC_INTMSK_HST_CHAN BIT(25)
+#define DWC_INTMSK_HST_PORT BIT(24)
+#define DWC_INTMSK_DATA_FETCH_SUS BIT(23)
+#define DWC_INTMSK_INCMP_PTX BIT(22)
+#define DWC_INTMSK_INCMP_OUT_PTX BIT(21)
+#define DWC_INTMSK_INCMP_IN_ATX BIT(20)
+#define DWC_INTMSK_OUT_ENDP BIT(19)
+#define DWC_INTMSK_IN_ENDP BIT(18)
+#define DWC_INTMSK_ENDP_MIS_MTCH BIT(17)
+#define DWC_INTMSK_END_OF_PFRM BIT(15)
+#define DWC_INTMSK_ISYNC_OUTPKT_DRP BIT(14)
+#define DWC_INTMSK_ENUM_DONE BIT(13)
+#define DWC_INTMSK_USB_RST BIT(12)
+#define DWC_INTMSK_USB_SUSP BIT(11)
+#define DWC_INTMSK_EARLY_SUSP BIT(10)
+#define DWC_INTMSK_I2C_INTR BIT(9)
+#define DWC_INTMSK_GLBL_OUT_NAK BIT(7)
+#define DWC_INTMSK_GLBL_IN_NAK BIT(6)
+#define DWC_INTMSK_NP_TXFIFO_EMPT BIT(5)
+#define DWC_INTMSK_RXFIFO_NOT_EMPT BIT(4)
+#define DWC_INTMSK_STRT_OF_FRM BIT(3)
+#define DWC_INTMSK_OTG BIT(2)
+#define DWC_INTMSK_MODE_MISMTC BIT(1)
+/*
+ * These Macros represents the bit fields of the Core Interrupt Register
+ * (GINTSTS). Set/clear the bits using the bit fields then write the u32 value
+ * to the register.
+ */
+#define DWC_INTSTS_WKP BIT(31)
+#define DWC_INTSTS_NEW_SES_DET BIT(30)
+#define DWC_INTSTS_SES_DISCON_DET BIT(29)
+#define DWC_INTSTS_CON_ID_STS_CHG BIT(28)
+#define DWC_INTSTS_P_TXFIFO_EMPTY BIT(26)
+#define DWC_INTSTS_HST_CHAN BIT(25)
+#define DWC_INTSTS_HST_PORT BIT(24)
+#define DWC_INTSTS_DATA_FETCH_SUS BIT(23)
+#define DWC_INTSTS_INCMP_PTX BIT(22)
+#define DWC_INTSTS_INCMP_OUT_PTX BIT(21)
+#define DWC_INTSTS_INCMP_IN_ATX BIT(20)
+#define DWC_INTSTS_OUT_ENDP BIT(19)
+#define DWC_INTSTS_IN_ENDP BIT(18)
+#define DWC_INTSTS_ENDP_MIS_MTCH BIT(17)
+#define DWC_INTSTS_END_OF_PFRM BIT(15)
+#define DWC_INTSTS_ISYNC_OUTPKT_DRP BIT(14)
+#define DWC_INTSTS_ENUM_DONE BIT(13)
+#define DWC_INTSTS_USB_RST BIT(12)
+#define DWC_INTSTS_USB_SUSP BIT(11)
+#define DWC_INTSTS_EARLY_SUSP BIT(10)
+#define DWC_INTSTS_I2C_INTR BIT(9)
+#define DWC_INTSTS_GLBL_OUT_NAK BIT(7)
+#define DWC_INTSTS_GLBL_IN_NAK BIT(6)
+#define DWC_INTSTS_NP_TXFIFO_EMPT BIT(5)
+#define DWC_INTSTS_RXFIFO_NOT_EMPT BIT(4)
+#define DWC_INTSTS_STRT_OF_FRM BIT(3)
+#define DWC_INTSTS_OTG BIT(2)
+#define DWC_INTSTS_MODE_MISMTC BIT(1)
+#define DWC_INTSTS_CURR_MODE BIT(0)
+#define DWC_SOF_INTR_MASK 0x0008
+#define DWC_HOST_MODE 1
+
+/*
+ * These Macros represents the bit fields in the Device Receive Status Read and
+ * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the u32
+ * element then read out the bits using the bit elements.
+ */
+#define DWC_DM_RXSTS_PKT_STS (0x01f << 17)
+#define DWC_DM_RXSTS_PKT_DPID (0x003 << 15)
+#define DWC_DM_RXSTS_BYTE_CNT (0x7ff << 4)
+#define DWC_DM_RXSTS_CHAN_NUM (0x00f << 0)
+
+#define DWC_DM_RXSTS_PKT_STS_RD(reg) (((reg) & (0x00f << 17)) >> 17)
+#define DWC_DM_RXSTS_PKT_DPID_RD(reg) (((reg) & (0x003 << 15)) >> 15)
+#define DWC_DM_RXSTS_BYTE_CNT_RD(reg) (((reg) & (0x7ff << 04)) >> 04)
+#define DWC_DM_RXSTS_CHAN_NUM_RD(reg) ((reg) & 0x00f)
+
+#define DWC_STS_DATA_UPDT 0x2 /* OUT Data Packet */
+#define DWC_STS_XFER_COMP 0x3 /* OUT Data Transfer Complete */
+#define DWC_DSTS_GOUT_NAK 0x1 /* Global OUT NAK */
+#define DWC_DSTS_SETUP_COMP 0x4 /* Setup Phase Complete */
+#define DWC_DSTS_SETUP_UPDT 0x6 /* SETUP Packet */
+
+/*
+ * These Macros represents the bit fields in the Host Receive Status Read and
+ * Pop Registers (GRXSTSR, GRXSTSP) Read the register into the u32
+ * element then read out the bits using the bit elements.
+ */
+#define DWC_HM_RXSTS_FRM_NUM (0x00f << 21)
+#define DWC_HM_RXSTS_PKT_STS (0x01f << 17)
+#define DWC_HM_RXSTS_PKT_DPID (0x003 << 15)
+#define DWC_HM_RXSTS_BYTE_CNT (0x7ff << 4)
+#define DWC_HM_RXSTS_CHAN_NUM (0x00f << 0)
+
+#define DWC_HM_RXSTS_PKT_STS_RD(reg) (((reg) & (0x00f << 17)) >> 17)
+#define DWC_HM_RXSTS_PKT_DPID_RD(reg) (((reg) & (0x003 << 15)) >> 15)
+#define DWC_HM_RXSTS_BYTE_CNT_RD(reg) (((reg) & (0x7ff << 04)) >> 04)
+#define DWC_HM_RXSTS_CHAN_NUM_RD(reg) ((reg) & 0x00f)
+
+#define DWC_GRXSTS_PKTSTS_IN 0x2
+#define DWC_GRXSTS_PKTSTS_IN_XFER_COMP 0x3
+#define DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR 0x5
+#define DWC_GRXSTS_PKTSTS_CH_HALTED 0x7
+
+/*
+ * These Macros represents the bit fields in the FIFO Size Registers (HPTXFSIZ,
+ * GNPTXFSIZ, DPTXFSIZn). Read the register into the u32 element then
+ * read out the bits using the bit elements.
+ */
+#define DWC_TX_FIFO_DEPTH_RD(reg) (((reg) & ((u32)0xffff << 16)) >> 16)
+#define DWC_TX_FIFO_DEPTH_WR(reg, x) \
+ (((reg) & (~((u32)0xffff << 16))) | ((x) << 16))
+#define DWC_TX_FIFO_START_ADDR_RD(reg) ((reg) & 0xffff)
+#define DWC_TX_FIFO_START_ADDR_WR(reg, x) \
+ (((reg) & (~((u32)0xffff))) | (x))
+
+/*
+ * These Macros represents the bit fields in the Non-Periodic Tx FIFO/Queue
+ * Status Register (GNPTXSTS). Read the register into the u32 element then read
+ * out the bits using the bit elements.
+ */
+#define DWC_GNPTXSTS_NPTXQTOP_CHNEP_RD(x) (((x) & (0x3f << 26)) >> 26)
+#define DWC_GNPTXSTS_NPTXQTOP_TKN_RD(x) (((x) & (0x03 << 24)) >> 24)
+#define DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(x) (((x) & (0xff << 16)) >> 16)
+#define DWC_GNPTXSTS_NPTXFSPCAVAIL_RD(x) (0xffff & (x))
+
+/*
+ * These Macros represents the bit fields in the Transmit FIFO Status Register
+ * (DTXFSTS). Read the register into the u32 element then read out the bits
+ * using the bit elements.
+ */
+#define DWC_DTXFSTS_TXFSSPC_AVAI_RD(x) ((x) & 0xffff)
+
+/*
+ * These Macros represents the bit fields in the I2C Control Register (I2CCTL).
+ * Read the register into the u32 element then read out the bits using the bit
+ * elements.
+ */
+#define DWC_I2CCTL_BSYDNE BIT(31)
+#define DWC_I2CCTL_RW BIT(30)
+#define DWC_I2CCTL_I2CDEVADDR(x) ((x) << 27)
+#define DWC_I2CCTL_I2CSUSCTL BIT(25)
+#define DWC_I2CCTL_ACK BIT(24)
+#define DWC_I2CCTL_I2CEN BIT(23)
+#define DWC_I2CCTL_ADDR BIT(22)
+#define DWC_I2CCTL_REGADDR(x) ((x) << 14)
+#define DWC_I2CCTL_RWDATA(x) ((x) << 6)
+
+/*
+ * These Macros represents the bit fields in the User HW Config1 Register. Read
+ * the register into the u32 element then read out the bits using the bit
+ * elements.
+ */
+#define DWC_HWCFG1_EPDIR15(x) ((x) << 30)
+#define DWC_HWCFG1_EPDIR14(x) ((x) << 28)
+#define DWC_HWCFG1_EPDIR13(x) ((x) << 26)
+#define DWC_HWCFG1_EPDIR12(x) ((x) << 24)
+#define DWC_HWCFG1_EPDIR11(x) ((x) << 22)
+#define DWC_HWCFG1_EPDIR10(x) ((x) << 20)
+#define DWC_HWCFG1_EPDIR9(x) ((x) << 18)
+#define DWC_HWCFG1_EPDIR8(x) ((x) << 16)
+#define DWC_HWCFG1_EPDIR7(x) ((x) << 14)
+#define DWC_HWCFG1_EPDIR6(x) ((x) << 13)
+#define DWC_HWCFG1_EPDIR5(x) ((x) << 10)
+#define DWC_HWCFG1_EPDIR4(x) ((x) << 08)
+#define DWC_HWCFG1_EPDIR3(x) ((x) << 06)
+#define DWC_HWCFG1_EPDIR2(x) ((x) << 04)
+#define DWC_HWCFG1_EPDIR1(x) ((x) << 02)
+#define DWC_HWCFG1_EPDIR0(x) ((x) << 00)
+
+/*
+ * These Macros represents the bit fields in the User HW Config2 Register. Read
+ * the register into the u32 element then read out the bits using the bit
+ * elements.
+ */
+#define DWC_HWCFG2_DEV_TKN_Q_DEPTH_RD(x) (((x) & (0x1F << 26)) >> 26)
+#define DWC_HWCFG2_HOST_PERIO_Q_DEPTH_RD(x) (((x) & (0x3 << 24)) >> 24)
+#define DWC_HWCFG2_NP_TX_Q_DEPTH_RD(x) (((x) & (0x3 << 22)) >> 22)
+#define DWC_HWCFG2_RX_STS_Q_DEPTH_RD(x) (((x) & (0x3 << 20)) >> 20)
+#define DWC_HWCFG2_DYN_FIFO_RD(x) (((x) & (0x1 << 19)) >> 19)
+#define DWC_HWCFG2_PERIO_EP_SUPP_RD(x) (((x) & (0x1 << 18)) >> 18)
+#define DWC_HWCFG2_NO_HST_CHAN_RD(x) (((x) & (0xf << 14)) >> 14)
+#define DWC_HWCFG2_NO_DEV_EP_RD(x) (((x) & (0xf << 10)) >> 10)
+#define DWC_HWCFG2_FS_PHY_TYPE_RD(x) (((x) & (0x3 << 8)) >> 8)
+#define DWC_HWCFG2_HS_PHY_TYPE_RD(x) (((x) & (0x3 << 06)) >> 06)
+#define DWC_HWCFG2_P_2_P_RD(x) (((x) & (0x1 << 05)) >> 05)
+#define DWC_HWCFG2_ARCH_RD(x) (((x) & (0x3 << 03)) >> 03)
+#define DWC_HWCFG2_OP_MODE_RD(x) ((x) & 0x7)
+
+#define DWC_HWCFG2_HS_PHY_TYPE_NOT_SUPPORTED 0
+#define DWC_HWCFG2_HS_PHY_TYPE_UTMI 1
+#define DWC_HWCFG2_HS_PHY_TYPE_ULPI 2
+#define DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI 3
+#define DWC_HWCFG2_OP_MODE_HNP_SRP_CAPABLE_OTG 0
+#define DWC_HWCFG2_OP_MODE_SRP_ONLY_CAPABLE_OTG 1
+#define DWC_HWCFG2_OP_MODE_NO_HNP_SRP_CAPABLE_OTG 2
+#define DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE 3
+#define DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE 4
+#define DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST 5
+#define DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST 6
+
+/*
+ * These Macros represents the bit fields in the User HW Config3 Register. ead
+ * the register into the u32 element then read out the bits using the bit
+ * elements.
+ */
+#define DWC_HWCFG3_DFIFO_DEPTH_RD(x) (((x) & (0xffff << 16)) >> 16)
+#define DWC_HWCFG3_AHB_PHY_CLK_SYNC_RD(x) (((x) & (0x1 << 12)) >> 12)
+#define DWC_HWCFG3_SYNC_RST_TYPE_RD(x) (((x) & (0x1 << 11)) >> 11)
+#define DWC_HWCFG3_OPT_FEATURES_RD(x) (((x) & (0x1 << 10)) >> 10)
+#define DWC_HWCFG3_VEND_CTRL_IF_RD(x) (((x) & (0x1 << 9)) >> 9)
+#define DWC_HWCFG3_I2C_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_HWCFG3_OTG_FUNC_RD(x) (((x) & (0x1 << 07)) >> 07)
+#define DWC_HWCFG3_PKTSIZE_CTR_WIDTH_RD(x) (((x) & (0x7 << 04)) >> 04)
+#define DWC_HWCFG3_XFERSIZE_CTR_WIDTH_RD(x) ((x) & 0xf)
+
+/*
+ * These Macros represents the bit fields in the User HW Config4 Register. Read
+ * the register into the u32 element then read out the bits using the bit
+ * elements.
+ */
+#define DWC_HWCFG4_NUM_IN_EPS_RD(x) (((x) & (0xF << 26)) >> 26)
+#define DWC_HWCFG4_DED_FIFO_ENA_RD(x) (((x) & (0x1 << 25)) >> 25)
+#define DWC_HWCFG4_SES_END_FILT_EN_RD(x) (((x) & (0x1 << 24)) >> 24)
+#define DWC_HWCFG4_BVALID_FILT_EN_RD(x) (((x) & (0x1 << 23)) >> 23)
+#define DWC_HWCFG4_AVALID_FILT_EN_RD(x) (((x) & (0x1 << 22)) >> 22)
+#define DWC_HWCFG4_VBUS_VALID_FILT_EN_RD(x) (((x) & (0x1 << 21)) >> 21)
+#define DWC_HWCFG4_IDDIG_FILT_EN_RD(x) (((x) & (0x1 << 20)) >> 20)
+#define DWC_HWCFG4_NUM_DEV_MODE_CTRL_EP_RD(x) (((x) & (0xF << 16)) >> 16)
+#define DWC_HWCFG4_UTMI_PHY_DATA_WIDTH_RD(x) (((x) & (0x3 << 14)) >> 14)
+#define DWC_HWCFG4_MIN_AHB_FREQ_RD(x) (((x) & (0x1 << 05)) >> 05)
+#define DWC_HWCFG4_POWER_OPT_RD(x) (((x) & (0x1 << 04)) >> 04)
+#define DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(x) ((x) & 0xf)
+
+/*
+ * Device Global Registers. Offsets 800h-BFFh
+ *
+ * The following structures define the size and relative field offsets for the
+ * Device Mode Registers.
+ *
+ * These registers are visible only in Device mode and must not be accessed in
+ * Host mode, as the results are unknown.
+ */
+#define DWC_DCFG 0x000
+#define DWC_DCTL 0x004
+#define DWC_DSTS 0x008
+#define DWC_DIEPMSK 0x010
+#define DWC_DOEPMSK 0x014
+#define DWC_DAINT 0x018
+#define DWC_DAINTMSK 0x01C
+#define DWC_DTKNQR1 0x020
+#define DWC_DTKNQR2 0x024
+#define DWC_DVBUSDIS 0x028
+#define DWC_DVBUSPULSE 0x02C
+#define DWC_DTKNQR3_DTHRCTL 0x030
+#define DWC_DTKNQR4FIFOEMPTYMSK 0x034
+
+/*
+ * These Macros represents the bit fields in the Device Configuration
+ * Register. Read the register into the u32 member then
+ * set/clear the bits using the bit elements. Write the
+ * u32 member to the dcfg register.
+*/
+#define DWC_DCFG_IN_EP_MISMATCH_CNT_RD(x) (((x) & (0x1f << 18)) >> 18)
+#define DWC_DCFG_P_FRM_INTRVL_RD(x) (((x) & (0x03 << 11)) >> 11)
+#define DWC_DCFG_DEV_ADDR_RD(x) (((x) & (0x3f << 04)) >> 04)
+#define DWC_DCFG_NGL_STS_OUT_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_DCFG_DEV_SPEED_RD(x) ((x) & 0x3)
+
+#define DWC_DCFG_IN_EP_MISMATCH_CNT_WR(reg, x) \
+ (((reg) & (~((u32)0x1f << 18))) | ((x) << 18))
+#define DWC_DCFG_P_FRM_INTRVL_WR(reg, x) \
+ (((reg) & (~((u32)0x03 << 11))) | ((x) << 11))
+#define DWC_DCFG_DEV_ADDR_WR(reg, x) \
+ (((reg) & (~((u32)0x3f << 04))) | ((x) << 04))
+#define DWC_DCFG_NGL_STS_OUT_WR(reg, x) \
+ (((reg) & (~((u32)0x1 << 2))) | ((x) << 2))
+#define DWC_DCFG_DEV_SPEED_WR(reg, x) \
+ (((reg) & (~(u32)0x3)) | (x))
+
+#define DWC_DCFG_FRAME_INTERVAL_80 0
+#define DWC_DCFG_FRAME_INTERVAL_85 1
+#define DWC_DCFG_FRAME_INTERVAL_90 2
+#define DWC_DCFG_FRAME_INTERVAL_95 3
+
+/*
+ * These Macros represents the bit fields in the Device Control Register. Read
+ * the register into the u32 member then set/clear the bits using the bit
+ * elements.
+ */
+#define DWC_DCTL_PWR_ON_PROG_DONE_RD(x) (((x) & (1 << 11)) >> 11)
+
+#define DWC_DCTL_PWR_ON_PROG_DONE_WR(reg, x) \
+ (((reg) & (~((u32)0x01 << 11))) | ((x) << 11))
+#define DWC_DCTL_CLR_GLBL_OUT_NAK_WR(reg, x) \
+ (((reg) & (~((u32)0x01 << 10))) | ((x) << 10))
+#define DWC_DCTL_SET_GLBL_OUT_NAL(reg, x) \
+ (((reg) & (~((u32)0x01 << 9))) | ((x) << 9))
+#define DWC_DCTL_CLR_CLBL_NP_IN_NAK(reg, x) \
+ (((reg) & (~((u32)0x01 << 8))) | ((x) << 8))
+#define DWC_DCTL_SET_GLBL_NP_IN_NAK(reg, x) \
+ (((reg) & (~((u32)0x01 << 07))) | ((x) << 07))
+#define DWC_DCTL_TST_CTL(reg, x) \
+ (((reg) & (~((u32)0x07 << 04))) | ((x) << 04))
+#define DWC_DCTL_GLBL_OUT_NAK_STS(reg, x) \
+ (((reg) & (~((u32)0x01 << 03))) | ((x) << 03))
+#define DWC_DCTL_GLBL_NP_IN_NAK(reg, x) \
+ (((reg) & (~((u32)0x01 << 02))) | ((x) << 02))
+#define DWC_DCTL_SFT_DISCONNECT(reg, x) \
+ (((reg) & (~((u32)0x01 << 01))) | ((x) << 01))
+#define DEC_DCTL_REMOTE_WAKEUP_SIG(reg, x) \
+ (((reg) & (~((u32)0x01 << 00))) | ((x) << 00))
+
+/*
+ * These Macros represents the bit fields in the Dev Status Register. Read the
+ * register into the u32 member then set/clear the bits using the bit elements.
+ */
+#define DWC_DSTS_SOFFN_RD(x) (((x) & (0x3fff << 8)) >> 8)
+#define DWC_DSTS_ERRTICERR_RD(x) (((x) & (0x0001 << 3)) >> 3)
+#define DWC_DSTS_ENUM_SPEED_RD(x) (((x) & (0x0003 << 1)) >> 1)
+#define DWC_DSTS_SUSP_STS_RD(x) ((x) & 1)
+
+#define DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ 0
+#define DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ 1
+#define DWC_DSTS_ENUMSPD_LS_PHY_6MHZ 2
+#define DWC_DSTS_ENUMSPD_FS_PHY_48MHZ 3
+
+/*
+ * These Macros represents the bit fields in the Device IN EP Interrupt Register
+ * and the Device IN EP Common Mask Register.
+ *
+ * Read the register into the u32 member then set/clear the bits using the bit
+ * elements.
+ */
+#define DWC_DIEPINT_TXFIFO_UNDERN_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_DIEPINT_TXFIFO_EMPTY_RD(x) (((x) & (0x1 << 7)) >> 7)
+#define DWC_DIEPINT_IN_EP_NAK_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_DIEPINT_IN_TKN_EP_MISS_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_DIEPINT_IN_TKN_TX_EMPTY_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_DIEPINT_TOUT_COND_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_DIEPINT_AHB_ERROR_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_DIEPINT_EP_DISA_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_DIEPINT_TX_CMPL_RD(x) ((x) & 0x1)
+
+#define DWC_DIEPINT_TXFIFO_UNDERN_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 8))) | ((x) << 8))
+#define DWC_DIEPINT_TXFIFO_EMPTY_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 7))) | ((x) << 7))
+#define DWC_DIEPINT_IN_EP_NAK_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 6))) | ((x) << 6))
+#define DWC_DIEPINT_IN_TKN_EP_MISS_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 5))) | ((x) << 5))
+#define DWC_DIEPINT_IN_TKN_TX_EMPTY_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 4))) | ((x) << 4))
+#define DWC_DIEPINT_TOUT_COND_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 3))) | ((x) << 3))
+#define DWC_DIEPINT_AHB_ERROR_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 2))) | ((x) << 2))
+#define DWC_DIEPINT_EP_DISA_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 1))) | ((x) << 1))
+#define DWC_DIEPINT_TX_CMPL_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 0))) | ((x) << 0))
+
+#define DWC_DIEPMSK_TXFIFO_UNDERN_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 8))) | ((x) << 8))
+#define DWC_DIEPMSK_TXFIFO_EMPTY_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 7))) | ((x) << 7))
+#define DWC_DIEPMSK_IN_EP_NAK_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 6))) | ((x) << 6))
+#define DWC_DIEPMSK_IN_TKN_EP_MISS_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 5))) | ((x) << 5))
+#define DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 4))) | ((x) << 4))
+#define DWC_DIEPMSK_TOUT_COND_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 3))) | ((x) << 3))
+#define DWC_DIEPMSK_AHB_ERROR_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 2))) | ((x) << 2))
+#define DWC_DIEPMSK_EP_DISA_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 1))) | ((x) << 1))
+#define DWC_DIEPMSK_TX_CMPL_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 0))) | ((x) << 0))
+
+/*
+ * These Macros represents the bit fields in the Device OUT EP Itr Register
+ * and Device OUT EP Common Interrupt Mask Register.
+ *
+ * Read the register into the u32 member then set/clear the bits using the bit
+ * elements.
+ */
+#define DWC_DOEPINT_OUTPKT_ERR_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_DOEPINT_B2B_PKTS_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_DOEPINT_OUT_TKN_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_DOEPINT_SETUP_DONE_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_DOEPINT_AHB_ERROR_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_DOEPINT_EP_DISA_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_DOEPINT_TX_COMPL_RD(x) (((x) & (0x1 << 0)) >> 0)
+
+#define DWC_DOEPMSK_OUTPKT_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 8))) | ((x) << 8))
+#define DWC_DOEPMSK_B2B_PKTS_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 6))) | ((x) << 6))
+#define DWC_DOEPMSK_OUT_TKN_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 4))) | ((x) << 4))
+#define DWC_DOEPMSK_SETUP_DONE_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 3))) | ((x) << 3))
+#define DWC_DOEPMSK_AHB_ERROR_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 2))) | ((x) << 2))
+#define DWC_DOEPMSK_EP_DISA_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 1))) | ((x) << 1))
+#define DWC_DOEPMSK_TX_COMPL_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 0))) | ((x) << 0))
+
+/*
+ * These Macros represents the bit fields in the Device All EP Intr and Mask
+ * Registers. Read the register into the u32 member then set/clear the bits
+ * using the bit elements.
+ */
+#define DWC_DAINT_OUT_EP_RD(reg, ep) \
+ (((reg) & (1 << (ep + 16))) >> (ep + 16))
+#define DWC_DAINTMSK_OUT_EP_RW(reg, ep) \
+ (((reg) & (~(u32)(1 << (ep + 16)))) | (1 << (ep + 16)))
+#define DWC_DAINT_IN_EP_RD(reg, ep) (((reg) & (1 << ep)) >> ep)
+#define DWC_DAINTMSK_IN_EP_RW(reg, ep) \
+ (((reg) & (~(u32)(1 << ep))) | (1 << ep))
+#define DWC_DAINT_OUTEP15 BIT(31)
+#define DWC_DAINT_OUTEP14 BIT(30)
+#define DWC_DAINT_OUTEP13 BIT(29)
+#define DWC_DAINT_OUTEP12 BIT(28)
+#define DWC_DAINT_OUTEP11 BIT(27)
+#define DWC_DAINT_OUTEP10 BIT(26)
+#define DWC_DAINT_OUTEP09 BIT(25)
+#define DWC_DAINT_OUTEP08 BIT(24)
+#define DWC_DAINT_OUTEP07 BIT(23)
+#define DWC_DAINT_OUTEP06 BIT(22)
+#define DWC_DAINT_OUTEP05 BIT(21)
+#define DWC_DAINT_OUTEP04 BIT(20)
+#define DWC_DAINT_OUTEP03 BIT(19)
+#define DWC_DAINT_OUTEP02 BIT(18)
+#define DWC_DAINT_OUTEP01 BIT(17)
+#define DWC_DAINT_OUTEP00 BIT(16)
+#define DWC_DAINT_INEP15 BIT(15)
+#define DWC_DAINT_INEP14 BIT(14)
+#define DWC_DAINT_INEP13 BIT(13)
+#define DWC_DAINT_INEP12 BIT(12)
+#define DWC_DAINT_INEP11 BIT(11)
+#define DWC_DAINT_INEP10 BIT(10)
+#define DWC_DAINT_INEP09 BIT(9)
+#define DWC_DAINT_INEP08 BIT(8)
+#define DWC_DAINT_INEP07 BIT(7)
+#define DWC_DAINT_INEP06 BIT(6)
+#define DWC_DAINT_INEP05 BIT(5)
+#define DWC_DAINT_INEP04 BIT(4)
+#define DWC_DAINT_INEP03 BIT(3)
+#define DWC_DAINT_INEP02 BIT(2)
+#define DWC_DAINT_INEP01 BIT(1)
+#define DWC_DAINT_INEP00 BIT(0)
+
+/*
+ * These Macros represents the bit fields in the Device IN Token Queue Read
+ * Registers. Read the register into the u32 member. READ-ONLY Register
+ */
+#define DWC_DTKNQR1_EP_TKN_NO_RD(x) (((x) & (0xffffff << 8)) >> 8)
+#define DWC_DTKNQR1_WRAP_BIT_RD(x) (((x) & (1 << 7)) >> 7)
+#define DWC_DTKNQR1_INT_TKN_Q_WR_PTR_RD(x) ((x) & 0x1f)
+
+/*
+ * These Macros represents Threshold control Register. Read and wr the register
+ * into the u32 member. READ-WRITABLE Register
+ */
+#define DWC_DTHCTRL_RX_ARB_PARK_EN_RD(x) (((x) & (0x001 << 27)) >> 27)
+#define DWC_DTHCTRL_RX_THR_LEN_RD(x) (((x) & (0x1ff << 17)) >> 17)
+#define DWC_DTHCTRL_RX_THR_EN_RD(x) (((x) & (0x001 << 16)) >> 16)
+#define DWC_DTHCTRL_TX_THR_LEN_RD(x) (((x) & (0x1ff << 02)) >> 02)
+#define DWC_DTHCTRL_ISO_THR_EN(x) (((x) & (0x001 << 01)) >> 01)
+#define DWC_DTHCTRL_NON_ISO_THR_ENA_RD(x) (((x) & (0x001 << 00)) >> 00)
+
+#define DWC_DTHCTRL_RX_ARB_PARK_EN_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 27))) | ((x) << 27))
+#define DWC_DTHCTRL_RX_THR_LEN_RW(reg, x) \
+ (((reg) & (~((u32)0x1ff << 17))) | ((x) << 17))
+#define DWC_DTHCTRL_RX_THR_EN_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 16))) | ((x) << 16))
+#define DWC_DTHCTRL_TX_THR_LEN_RW(reg, x) \
+ (((reg) & (~((u32)0x1ff << 02))) | ((x) << 02))
+#define DWC_DTHCTRL_ISO_THR_EN_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 01))) | ((x) << 01))
+#define DWC_DTHCTRL_NON_ISO_THR_ENA_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 00))) | ((x) << 00))
+
+/*
+ * Device Logical IN Endpoint-Specific Registers. Offsets 900h-AFCh
+ *
+ * There will be one set of endpoint registers per logical endpoint implemented.
+ *
+ * These registers are visible only in Device mode and must not be accessed in
+ * Host mode, as the results are unknown.
+ */
+#define DWC_DIEPCTL 0x00
+#define DWC_DIEPINT 0x08
+#define DWC_DIEPTSIZ 0x10
+#define DWC_DIEPDMA 0x14
+#define DWC_DTXFSTS 0x18
+
+/*
+ * Device Logical OUT Endpoint-Specific Registers. Offsets: B00h-CFCh
+ *
+ * There will be one set of endpoint registers per logical endpoint implemented.
+ *
+ * These registers are visible only in Device mode and must not be accessed in
+ * Host mode, as the results are unknown.
+ */
+#define DWC_DOEPCTL 0x00
+#define DWC_DOEPFN 0x04
+#define DWC_DOEPINT 0x08
+#define DWC_DOEPTSIZ 0x10
+#define DWC_DOEPDMA 0x14
+
+/*
+ * These Macros represents the bit fields in the Device EP Ctrl Register. Read
+ * the register into the u32 member then set/clear the bits using the bit
+ * elements.
+ */
+#define DWC_DEP0CTL_MPS_64 0
+#define DWC_DEP0CTL_MPS_32 1
+#define DWC_DEP0CTL_MPS_16 2
+#define DWC_DEP0CTL_MPS_8 3
+
+#define DWC_DEPCTL_EPENA_RD(x) (((x) & (0x1 << 31)) >> 31)
+#define DWC_DEPCTL_EPDIS_RD(x) (((x) & (0x1 << 30)) >> 30)
+#define DWC_DEPCTL_SET_DATA1_PID_RD(x) (((x) & (0x1 << 29)) >> 29)
+#define DWC_DEPCTL_SET_DATA0_PID_RD(x) (((x) & (0x1 << 28)) >> 28)
+#define DWC_DEPCTL_SET_NAK_RD(x) (((x) & (0x1 << 27)) >> 27)
+#define DWC_DEPCTL_CLR_NAK_RD(x) (((x) & (0x1 << 26)) >> 26)
+#define DWC_DEPCTL_TX_FIFO_NUM_RD(x) (((x) & (0xf << 22)) >> 22)
+#define DWC_DEPCTL_STALL_HNDSHK _RD(x) (((x) & (0x1 << 21)) >> 21)
+#define DWC_DEPCTL_SNP_MODE_RD(x) (((x) & (0x1 << 20)) >> 20)
+#define DWC_DEPCTL_EP_TYPE_RD(x) (((x) & (0x3 << 18)) >> 18)
+#define DWC_DEPCTL_NKASTS_RD(x) (((x) & (0x1 << 17)) >> 17)
+#define DWC_DEPCTL_DPID _RD(x) (((x) & (0x1 << 16)) >> 16)
+#define DWC_DEPCTL_ACT_EP_RD(x) (((x) & (0x1 << 15)) >> 15)
+#define DWC_DEPCTL_NXT_EP_RD(x) (((x) & (0xf << 11)) >> 11)
+#define DWC_DEPCTL_MPS_RD(x) (((x) & (0x7ff << 00)) >> 00)
+
+#define DWC_DEPCTL_EPENA_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 31))) | ((x) << 31))
+#define DWC_DEPCTL_EPDIS_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 30))) | ((x) << 30))
+#define DWC_DEPCTL_SET_DATA1_PID_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 29))) | ((x) << 29))
+#define DWC_DEPCTL_SET_DATA0_PID_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 28))) | ((x) << 28))
+#define DWC_DEPCTL_SET_NAK_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 27))) | ((x) << 27))
+#define DWC_DEPCTL_CLR_NAK_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 26))) | ((x) << 26))
+#define DWC_DEPCTL_TX_FIFO_NUM_RW(reg, x) \
+ (((reg) & (~((u32)0x00f << 22))) | ((x) << 22))
+#define DWC_DEPCTL_STALL_HNDSHK_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 21))) | ((x) << 21))
+#define DWC_DEPCTL_SNP_MODE_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 20))) | ((x) << 20))
+#define DWC_DEPCTL_EP_TYPE_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 18))) | ((x) << 18))
+#define DWC_DEPCTL_NKASTS_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 17))) | ((x) << 17))
+#define DWC_DEPCTL_DPID_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 16))) | ((x) << 16))
+#define DWC_DEPCTL_ACT_EP_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 15))) | ((x) << 15))
+#define DWC_DEPCTL_NXT_EP_RW(reg, x) \
+ (((reg) & (~((u32)0x00f << 11))) | ((x) << 11))
+#define DWC_DEPCTL_MPS_RW(reg, x) \
+ (((reg) & (~((u32)0x7ff << 00))) | ((x) << 00))
+
+/*
+ * These Macros represents the bit fields in the Device EP Txfer Size Register.
+ * Read the register into the u32 member then set/clear the bits using the bit
+ * elements.
+ */
+#if defined(DWC_LIMITED_XFER_SIZE)
+#define DWC_DEPTSIZ_MCOUNT_RD(x) (((x) & (0x003 << 29)) >> 29)
+#define DWC_DEPTSIZ_PKT_CNT_RD(x) (((x) & (0x01f << 19)) >> 19)
+#define DWC_DEPTSIZ_XFER_SIZ_RD(x) (((x) & (0x7ff << 00)) >> 00)
+#define DWC_DEPTSIZ_MCOUNT_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 29))) | ((x) << 29))
+#define DWC_DEPTSIZ_PKT_CNT_RW(reg, x) \
+ (((reg) & (~((u32)0x01f << 19))) | ((x) << 19))
+#define DWC_DEPTSIZ_XFER_SIZ_RW(reg, x) \
+ (((reg) & (~((u32)0x7ff << 00))) | ((x) << 00))
+#else
+#define DWC_DEPTSIZ_MCOUNT_RD(x) \
+ (((x) & (0x003 << 29)) >> 29)
+#define DWC_DEPTSIZ_PKT_CNT_RD(x) \
+ (((x) & (0x3ff << 19)) >> 19)
+#define DWC_DEPTSIZ_XFER_SIZ_RD(x) \
+ (((x) & (0x7ffff << 00)) >> 00)
+#define DWC_DEPTSIZ_MCOUNT_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 29))) | ((x) << 29))
+#define DWC_DEPTSIZ_PKT_CNT_RW(reg, x) \
+ (((reg) & (~((u32)0x7ff << 19))) | ((x) << 19))
+#define DWC_DEPTSIZ_XFER_SIZ_RW(reg, x) \
+ (((reg) & (~((u32)0x7ffff << 00))) | ((x) << 00))
+#endif
+
+/*
+ * These Macros represents the bit fields in the Device EP 0 Transfer Size
+ * Register. Read the register into the u32 member then set/clear the bits
+ * using the bit elements.
+ */
+#define DWC_DEPTSIZ0_SUPCNT_RD(x) (((x) & (0x003 << 29)) >> 29)
+#define DWC_DEPTSIZ0_PKT_CNT_RD(x) (((x) & (0x001 << 19)) >> 19)
+#define DWC_DEPTSIZ0_XFER_SIZ_RD(x) (((x) & (0x07f << 00)) >> 00)
+#define DWC_DEPTSIZ0_SUPCNT_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 29))) | ((x) << 29))
+#define DWC_DEPTSIZ0_PKT_CNT_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 19))) | ((x) << 19))
+#define DWC_DEPTSIZ0_XFER_SIZ_RW(reg, x) \
+ (((reg) & (~((u32)0x07f << 00))) | ((x) << 00))
+
+#define MAX_PERIO_FIFOS 15 /* Max periodic FIFOs */
+#define MAX_TX_FIFOS 15 /* Max non-periodic FIFOs */
+
+/* Maximum number of Endpoints/HostChannels */
+#define MAX_EPS_CHANNELS 16 /* This come from device tree or defconfig */
+
+/*
+ * The device_if structure contains information needed to manage the DWC_otg
+ * controller acting in device mode. It represents the programming view of the
+ * device-specific aspects of the controller.
+ */
+struct device_if {
+ /* Device Global Registers starting at offset 800h */
+ ulong dev_global_regs;
+#define DWC_DEV_GLOBAL_REG_OFFSET 0x800
+
+ /* Device Logical IN Endpoint-Specific Registers 900h-AFCh */
+ ulong in_ep_regs[MAX_EPS_CHANNELS];
+#define DWC_DEV_IN_EP_REG_OFFSET 0x900
+#define DWC_EP_REG_OFFSET 0x20
+
+ /* Device Logical OUT Endpoint-Specific Registers B00h-CFCh */
+ ulong out_ep_regs[MAX_EPS_CHANNELS];
+#define DWC_DEV_OUT_EP_REG_OFFSET 0xB00
+
+ /* Device configuration information */
+ /* Device Speed 0: Unknown, 1: LS, 2:FS, 3: HS */
+ u8 speed;
+ /* Number # of Tx EP range: 0-15 exept ep0 */
+ u8 num_in_eps;
+ /* Number # of Rx EP range: 0-15 exept ep 0 */
+ u8 num_out_eps;
+
+ /* Size of periodic FIFOs (Bytes) */
+ u16 perio_tx_fifo_size[MAX_PERIO_FIFOS];
+
+ /* Size of Tx FIFOs (Bytes) */
+ u16 tx_fifo_size[MAX_TX_FIFOS];
+
+ /* Thresholding enable flags and length varaiables */
+ u16 rx_thr_en;
+ u16 iso_tx_thr_en;
+ u16 non_iso_tx_thr_en;
+ u16 rx_thr_length;
+ u16 tx_thr_length;
+};
+
+/*
+ * These Macros represents the bit fields in the Power and Clock Gating Control
+ * Register. Read the register into the u32 member then set/clear the
+ * bits using the bit elements.
+ */
+#define DWC_PCGCCTL_PHY_SUS_RD(x) (((x) & (0x001 << 4)) >> 4)
+#define DWC_PCGCCTL_RSTP_DWN_RD(x) (((x) & (0x001 << 3)) >> 3)
+#define DWC_PCGCCTL_PWR_CLAMP_RD(x) (((x) & (0x001 << 2)) >> 2)
+#define DWC_PCGCCTL_GATE_HCLK_RD(x) (((x) & (0x001 << 1)) >> 1)
+#define DWC_PCGCCTL_STOP_CLK_RD(x) (((x) & (0x001 << 0)) >> 0)
+
+#define DWC_PCGCCTL_RSTP_DWN_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 3))) | ((x) << 3))
+#define DWC_PCGCCTL_PWR_CLAMP_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 2))) | ((x) << 2))
+#define DWC_PCGCCTL_GATE_HCLK_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 1))) | ((x) << 1))
+#define DWC_PCGCCTL_STOP_CLK_SET(reg) \
+ (((reg) | 1))
+#define DWC_PCGCCTL_STOP_CLK_CLR(reg) \
+ (((reg) & (~((u32)0x001 << 0))))
+
+/*
+ * Host Mode Register Structures
+ */
+
+/*
+ * The Host Global Registers structure defines the size and relative field
+ * offsets for the Host Mode Global Registers. Host Global Registers offsets
+ * 400h-7FFh.
+*/
+#define DWC_HCFG 0x00
+#define DWC_HFIR 0x04
+#define DWC_HFNUM 0x08
+#define DWC_HPTXSTS 0x10
+#define DWC_HAINT 0x14
+#define DWC_HAINTMSK 0x18
+
+/*
+ * These Macros represents the bit fields in the Host Configuration Register.
+ * Read the register into the u32 member then set/clear the bits using the bit
+ * elements. Write the u32 member to the hcfg register.
+ */
+#define DWC_HCFG_FSLSUPP_RD(x) (((x) & (0x001 << 2)) >> 2)
+#define DWC_HCFG_FSLSP_CLK_RD(x) (((x) & (0x003 << 0)) >> 0)
+#define DWC_HCFG_FSLSUPP_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 2))) | ((x) << 2))
+#define DWC_HCFG_FSLSP_CLK_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 0))) | ((x) << 0))
+
+#define DWC_HCFG_30_60_MHZ 0
+#define DWC_HCFG_48_MHZ 1
+#define DWC_HCFG_6_MHZ 2
+
+/*
+ * These Macros represents the bit fields in the Host Frame Remaing/Number
+ * Register.
+ */
+#define DWC_HFIR_FRINT_RD(x) (((x) & (0xffff << 0)) >> 0)
+#define DWC_HFIR_FRINT_RW(reg, x) \
+ (((reg) & (~((u32)0xffff << 0))) | ((x) << 0))
+
+/*
+ * These Macros represents the bit fields in the Host Frame Remaing/Number
+ * Register.
+ */
+#define DWC_HFNUM_FRREM_RD(x) (((x) & (0xffff << 16)) >> 16)
+#define DWC_HFNUM_FRNUM_RD(x) (((x) & (0xffff << 0)) >> 0)
+#define DWC_HFNUM_FRREM_RW(reg, x) \
+ (((reg) & (~((u32)0xffff << 16))) | ((x) << 16))
+#define DWC_HFNUM_FRNUM_RW(reg, x) \
+ (((reg) & (~((u32)0xffff << 0))) | ((x) << 0))
+#define DWC_HFNUM_MAX_FRNUM 0x3FFF
+#define DWC_HFNUM_MAX_FRNUM 0x3FFF
+
+#define DWC_HPTXSTS_PTXQTOP_ODD_RD(x) (((x) & (0x01 << 31)) >> 31)
+#define DWC_HPTXSTS_PTXQTOP_CHNUM_RD(x) (((x) & (0x0f << 27)) >> 27)
+#define DWC_HPTXSTS_PTXQTOP_TKN_RD(x) (((x) & (0x03 << 25)) >> 25)
+#define DWC_HPTXSTS_PTXQTOP_TERM_RD(x) (((x) & (0x01 << 24)) >> 24)
+#define DWC_HPTXSTS_PTXSPC_AVAIL_RD(x) (((x) & (0xff << 16)) >> 16)
+#define DWC_HPTXSTS_PTXFSPC_AVAIL_RD(x) (((x) & (0xffff << 00)) >> 00)
+
+/*
+ * These Macros represents the bit fields in the Host Port Control and Status
+ * Register. Read the register into the u32 member then set/clear the bits using
+ * the bit elements. Write the u32 member to the hprt0 register.
+ */
+#define DWC_HPRT0_PRT_SPD_RD(x) (((x) & (0x3 << 17)) >> 17)
+#define DWC_HPRT0_PRT_TST_CTL_RD(x) (((x) & (0xf << 13)) >> 13)
+#define DWC_HPRT0_PRT_PWR_RD(x) (((x) & (0x1 << 12)) >> 12)
+#define DWC_HPRT0_PRT_LSTS_RD(x) (((x) & (0x3 << 10)) >> 10)
+#define DWC_HPRT0_PRT_RST_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_HPRT0_PRT_SUS_RD(x) (((x) & (0x1 << 7)) >> 7)
+#define DWC_HPRT0_PRT_RES_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_HPRT0_PRT_OVRCURR_CHG_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_HPRT0_PRT_OVRCURR_ACT_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_HPRT0_PRT_ENA_DIS_CHG_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_HPRT0_PRT_ENA_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_HPRT0_PRT_CONN_DET_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_HPRT0_PRT_STS_RD(x) (((x) & (0x1 << 0)) >> 0)
+
+#define DWC_HPRT0_PRT_SPD_RW(reg, x) \
+ (((reg) & (~((u32)0x3 << 17))) | ((x) << 17))
+#define DWC_HPRT0_PRT_TST_CTL_RW(reg, x) \
+ (((reg) & (~((u32)0xf << 13))) | ((x) << 13))
+#define DWC_HPRT0_PRT_PWR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 12))) | ((x) << 12))
+#define DWC_HPRT0_PRT_LSTS_RW(reg, x) \
+ (((reg) & (~((u32)0x3 << 10))) | ((x) << 10))
+#define DWC_HPRT0_PRT_RST_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 8))) | ((x) << 8))
+#define DWC_HPRT0_PRT_SUS_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 7))) | ((x) << 7))
+#define DWC_HPRT0_PRT_RES_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 6))) | ((x) << 6))
+#define DWC_HPRT0_PRT_OVRCURR_CHG_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 5))) | ((x) << 5))
+#define DWC_HPRT0_PRT_OVRCURR_ACT_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 4))) | ((x) << 4))
+#define DWC_HPRT0_PRT_ENA_DIS_CHG_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 3))) | ((x) << 3))
+#define DWC_HPRT0_PRT_ENA_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 2))) | ((x) << 2))
+#define DWC_HPRT0_PRT_CONN_DET_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 1))) | ((x) << 1))
+#define DWC_HPRT0_PRT_STS_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 0))) | ((x) << 0))
+
+#define DWC_HPRT0_PRTSPD_HIGH_SPEED 0
+#define DWC_HPRT0_PRTSPD_FULL_SPEED 1
+#define DWC_HPRT0_PRTSPD_LOW_SPEED 2
+
+/*
+ * These Macros represents the bit fields in the Host All Interrupt Register.
+ */
+#define DWC_HAINT_CH15_RD(x) (((x) & (0x1 << 15)) >> 15)
+#define DWC_HAINT_CH14_RD(x) (((x) & (0x1 << 14)) >> 14)
+#define DWC_HAINT_CH13_RD(x) (((x) & (0x1 << 13)) >> 13)
+#define DWC_HAINT_CH12_RD(x) (((x) & (0x1 << 12)) >> 12)
+#define DWC_HAINT_CH11_RD(x) (((x) & (0x1 << 11)) >> 11)
+#define DWC_HAINT_CH10_RD(x) (((x) & (0x1 << 10)) >> 10)
+#define DWC_HAINT_CH09_RD(x) (((x) & (0x1 << 9)) >> 9)
+#define DWC_HAINT_CH08_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_HAINT_CH07_RD(x) (((x) & (0x1 << 7)) >> 7)
+#define DWC_HAINT_CH06_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_HAINT_CH05_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_HAINT_CH04_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_HAINT_CH03_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_HAINT_CH02_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_HAINT_CH01_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_HAINT_CH00_RD(x) (((x) & (0x1 << 0)) >> 0)
+
+#define DWC_HAINT_RD(x) (((x) & (0xffff << 0)) >> 0)
+
+/*
+ * These Macros represents the bit fields in the Host All Interrupt Register.
+ */
+#define DWC_HAINTMSK_CH15_RD(x) (((x) & (0x1 << 15)) >> 15)
+#define DWC_HAINTMSK_CH14_RD(x) (((x) & (0x1 << 14)) >> 14)
+#define DWC_HAINTMSK_CH13_RD(x) (((x) & (0x1 << 13)) >> 13)
+#define DWC_HAINTMSK_CH12_RD(x) (((x) & (0x1 << 12)) >> 12)
+#define DWC_HAINTMSK_CH11_RD(x) (((x) & (0x1 << 11)) >> 11)
+#define DWC_HAINTMSK_CH10_RD(x) (((x) & (0x1 << 10)) >> 10)
+#define DWC_HAINTMSK_CH09_RD(x) (((x) & (0x1 << 9)) >> 9)
+#define DWC_HAINTMSK_CH08_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_HAINTMSK_CH07_RD(x) (((x) & (0x1 << 7)) >> 7)
+#define DWC_HAINTMSK_CH06_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_HAINTMSK_CH05_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_HAINTMSK_CH04_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_HAINTMSK_CH03_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_HAINTMSK_CH02_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_HAINTMSK_CH01_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_HAINTMSK_CH00_RD(x) (((x) & (0x1 << 0)) >> 0)
+#define DWC_HAINTMSK_RD(x) ((x) & 0xffff)
+
+#define DWC_HAINTMSK_CH15_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 15))) | ((x) << 15))
+#define DWC_HAINTMSK_CH14_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 14))) | ((x) << 14))
+#define DWC_HAINTMSK_CH13_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 13))) | ((x) << 13))
+#define DWC_HAINTMSK_CH12_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 12))) | ((x) << 12))
+#define DWC_HAINTMSK_CH11_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 11))) | ((x) << 11))
+#define DWC_HAINTMSK_CH10_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 10))) | ((x) << 10))
+#define DWC_HAINTMSK_CH09_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 9))) | ((x) << 9))
+#define DWC_HAINTMSK_CH08_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 8))) | ((x) << 8))
+#define DWC_HAINTMSK_CH07_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 7))) | ((x) << 7))
+#define DWC_HAINTMSK_CH06_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 6))) | ((x) << 6))
+#define DWC_HAINTMSK_CH05_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 5))) | ((x) << 5))
+#define DWC_HAINTMSK_CH04_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 4))) | ((x) << 4))
+#define DWC_HAINTMSK_CH03_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 3))) | ((x) << 3))
+#define DWC_HAINTMSK_CH02_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 2))) | ((x) << 2))
+#define DWC_HAINTMSK_CH01_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 1))) | ((x) << 1))
+#define DWC_HAINTMSK_CH00_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 0))) | ((x) << 0))
+#define DWC_HAINTMSK_RW(reg, x) \
+ (((reg) & (~((u32)0xffff))) | x)
+
+/*
+ * Host Channel Specific Registers. 500h-5FCh
+ */
+#define DWC_HCCHAR 0x00
+#define DWC_HCSPLT 0x04
+#define DWC_HCINT 0x08
+#define DWC_HCINTMSK 0x0C
+#define DWC_HCTSIZ 0x10
+#define DWC_HCDMA 0x14
+
+/*
+ * These Macros represents the bit fields in the Host Channel Characteristics
+ * Register. Read the register into the u32 member then set/clear the bits using
+ * the bit elements. Write the u32 member to the hcchar register.
+ */
+#define DWC_HCCHAR_ENA_RD(x) (((x) & (0x001 << 31)) >> 31)
+#define DWC_HCCHAR_DIS_RD(x) (((x) & (0x001 << 30)) >> 30)
+#define DWC_HCCHAR_ODD_FRAME_RD(x) (((x) & (0x001 << 29)) >> 29)
+#define DWC_HCCHAR_DEV_ADDR_RD(x) (((x) & (0x07f << 22)) >> 22)
+#define DWC_HCCHAR_MULTI_CNT_RD(x) (((x) & (0x003 << 20)) >> 20)
+#define DWC_HCCHAR_EPTYPE_RD(x) (((x) & (0x003 << 18)) >> 18)
+#define DWC_HCCHAR_LSP_DEV_RD(x) (((x) & (0x001 << 17)) >> 17)
+#define DWC_HCCHAR_EPDIR_RD(x) (((x) & (0x001 << 15)) >> 15)
+#define DWC_HCCHAR_EP_NUM_RD(x) (((x) & (0x00f << 11)) >> 11)
+#define DWC_HCCHAR_MPS_RD(x) (((x) & (0x7ff << 0)) >> 0)
+
+#define DWC_HCCHAR_ENA_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 31))) | ((x) << 31))
+#define DWC_HCCHAR_DIS_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 30))) | ((x) << 30))
+#define DWC_HCCHAR_ODD_FRAME_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 29))) | ((x) << 29))
+#define DWC_HCCHAR_DEV_ADDR_RW(reg, x) \
+ (((reg) & (~((u32)0x07f << 22))) | ((x) << 22))
+#define DWC_HCCHAR_MULTI_CNT_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 20))) | ((x) << 20))
+#define DWC_HCCHAR_EPTYPE_RW(reg, x) \
+ (((reg) & (~((u32)0x003 << 18))) | ((x) << 18))
+#define DWC_HCCHAR_LSP_DEV_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 17))) | ((x) << 17))
+#define DWC_HCCHAR_EPDIR_RW(reg, x) \
+ (((reg) & (~((u32)0x001 << 15))) | ((x) << 15))
+#define DWC_HCCHAR_EP_NUM_RW(reg, x) \
+ (((reg) & (~((u32)0x00f << 11))) | ((x) << 11))
+#define DWC_HCCHAR_MPS_RW(reg, x) \
+ (((reg) & (~((u32)0x7ff << 0))) | ((x) << 0))
+
+#define DWC_HCSPLT_ENA_RD(x) (((x) & (0x01 << 31)) >> 31)
+#define DWC_HCSPLT_COMP_SPLT_RD(x) (((x) & (0x01 << 16)) >> 16)
+#define DWC_HCSPLT_TRANS_POS_RD(x) (((x) & (0x03 << 14)) >> 14)
+#define DWC_HCSPLT_HUB_ADDR_RD(x) (((x) & (0x7f << 7)) >> 7)
+#define DWC_HCSPLT_PRT_ADDR_RD(x) (((x) & (0x7f << 0)) >> 0)
+
+#define DWC_HCSPLT_ENA_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 31))) | ((x) << 31))
+#define DWC_HCSPLT_COMP_SPLT_RW(reg, x) \
+ (((reg) & (~((u32)0x01 << 16))) | ((x) << 16))
+#define DWC_HCSPLT_TRANS_POS_RW(reg, x) \
+ (((reg) & (~((u32)0x03 << 14))) | ((x) << 14))
+#define DWC_HCSPLT_HUB_ADDR_RW(reg, x) \
+ (((reg) & (~((u32)0x7f << 7))) | ((x) << 7))
+#define DWC_HCSPLT_PRT_ADDR_RW(reg, x) \
+ (((reg) & (~((u32)0x7f << 0))) | ((x) << 0))
+
+#define DWC_HCSPLIT_XACTPOS_MID 0
+#define DWC_HCSPLIT_XACTPOS_END 1
+#define DWC_HCSPLIT_XACTPOS_BEGIN 2
+#define DWC_HCSPLIT_XACTPOS_ALL 3
+
+/*
+ * These Macros represents the bit fields in the Host All Interrupt
+ * Register.
+ */
+#define DWC_HCINT_DATA_TOG_ERR_RD(x) (((x) & (0x1 << 10)) >> 10)
+#define DWC_HCINT_FRAME_OVERN_ERR_RD(x) (((x) & (0x1 << 9)) >> 9)
+#define DWC_HCINT_BBL_ERR_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_HCINT_TRANS_ERR_RD(x) (((x) & (0x1 << 7)) >> 7)
+#define DWC_HCINT_NYET_RESP_REC_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_HCINT_ACK_RESP_REC_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_HCINT_NAK_RESP_REC_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_HCINT_STALL_RESP_REC_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_HCINT_AHB_ERR_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_HCINT_CHAN_HALTED_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_HCINT_TXFER_CMPL_RD(x) (((x) & (0x1 << 0)) >> 0)
+
+#define DWC_HCINT_DATA_TOG_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 10))) | ((x) << 10))
+#define DWC_HCINT_FRAME_OVERN_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 9))) | ((x) << 9))
+#define DWC_HCINT_BBL_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 8))) | ((x) << 8))
+#define DWC_HCINT_TRANS_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 7))) | ((x) << 7))
+#define DWC_HCINT_NYET_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 6))) | ((x) << 6))
+#define DWC_HCINT_ACK_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 5))) | ((x) << 5))
+#define DWC_HCINT_NAK_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 4))) | ((x) << 4))
+#define DWC_HCINT_STALL_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 3))) | ((x) << 3))
+#define DWC_HCINT_AHB_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 2))) | ((x) << 2))
+#define DWC_HCINT_CHAN_HALTED_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 1))) | ((x) << 1))
+#define DWC_HCINT_TXFER_CMPL_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 0))) | ((x) << 0))
+
+/*
+ * These Macros represents the bit fields in the Host Channel Transfer Size
+ * Register. Read the register into the u32 member then set/clear the bits
+ * using the bit elements. Write the u32 member to the hcchar register.
+ */
+#define DWC_HCTSIZ_DO_PING_PROTO_RD(x) (((x) & (0x00001 << 31)) >> 31)
+#define DWC_HCTSIZ_PKT_PID_RD(x) (((x) & (0x00003 << 29)) >> 29)
+#define DWC_HCTSIZ_PKT_CNT_RD(x) (((x) & (0x003ff << 19)) >> 19)
+#define DWC_HCTSIZ_XFER_SIZE_RD(x) (((x) & (0x7ffff << 00)) >> 00)
+
+#define DWC_HCTSIZ_DO_PING_PROTO_RW(reg, x) \
+ (((reg) & (~((u32)0x00001 << 31))) | ((x) << 31))
+#define DWC_HCTSIZ_PKT_PID_RW(reg, x) \
+ (((reg) & (~((u32)0x00003 << 29))) | ((x) << 29))
+#define DWC_HCTSIZ_PKT_CNT_RW(reg, x) \
+ (((reg) & (~((u32)0x003ff << 19))) | ((x) << 19))
+#define DWC_HCTSIZ_XFER_SIZE_RW(reg, x) \
+ (((reg) & (~((u32)0x7ffff << 00))) | ((x) << 00))
+
+#define DWC_HCTSIZ_DATA0 0
+#define DWC_HCTSIZ_DATA1 2
+#define DWC_HCTSIZ_DATA2 1
+#define DWC_HCTSIZ_MDATA 3
+#define DWC_HCTSIZ_SETUP 3
+
+/*
+ * These Macros represents the bit fields in the Host Channel Interrupt Mask
+ * Register. Read the register into the u32 member then set/clear the bits using
+ * the bit elements. Write the u32 member to the hcintmsk register.
+ */
+#define DWC_HCINTMSK_DATA_TOG_ERR_RD(x) (((x) & (0x1 << 10)) >> 10)
+#define DWC_HCINTMSK_FRAME_OVERN_ERR_RD(x) (((x) & (0x1 << 9)) >> 9)
+#define DWC_HCINTMSK_BBL_ERR_RD(x) (((x) & (0x1 << 8)) >> 8)
+#define DWC_HCINTMSK_TRANS_ERR_RD(x) (((x) & (0x1 << 7)) >> 7)
+#define DWC_HCINTMSK_NYET_RESP_REC_RD(x) (((x) & (0x1 << 6)) >> 6)
+#define DWC_HCINTMSK_ACK_RESP_REC_RD(x) (((x) & (0x1 << 5)) >> 5)
+#define DWC_HCINTMSK_NAK_RESP_REC_RD(x) (((x) & (0x1 << 4)) >> 4)
+#define DWC_HCINTMSK_STALL_RESP_REC_RD(x) (((x) & (0x1 << 3)) >> 3)
+#define DWC_HCINTMSK_AHB_ERR_RD(x) (((x) & (0x1 << 2)) >> 2)
+#define DWC_HCINTMSK_CHAN_HALTED_RD(x) (((x) & (0x1 << 1)) >> 1)
+#define DWC_HCINTMSK_TXFER_CMPL_RD(x) (((x) & (0x1 << 0)) >> 0)
+
+#define DWC_HCINTMSK_DATA_TOG_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 10))) | ((x) << 10))
+#define DWC_HCINTMSK_FRAME_OVERN_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 9))) | ((x) << 9))
+#define DWC_HCINTMSK_BBL_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 8))) | ((x) << 8))
+#define DWC_HCINTMSK_TRANS_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 7))) | ((x) << 7))
+#define DWC_HCINTMSK_NYET_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 6))) | ((x) << 6))
+#define DWC_HCINTMSK_ACK_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 5))) | ((x) << 5))
+#define DWC_HCINTMSK_NAK_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 4))) | ((x) << 4))
+#define DWC_HCINTMSK_STALL_RESP_REC_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 3))) | ((x) << 3))
+#define DWC_HCINTMSK_AHB_ERR_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 2))) | ((x) << 2))
+#define DWC_HCINTMSK_CHAN_HALTED_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 1))) | ((x) << 1))
+#define DWC_HCINTMSK_TXFER_CMPL_RW(reg, x) \
+ (((reg) & (~((u32)0x1 << 0))) | ((x) << 0))
+
+/*
+ * OTG Host Interface Structure.
+ *
+ * The OTG Host Interface Structure structure contains information needed to
+ * manage the DWC_otg controller acting in host mode. It represents the
+ * programming view of the host-specific aspects of the controller.
+ */
+struct dwc_host_if { /* CONFIG_DWC_OTG_REG_LE */
+ /* Host Global Registers starting at offset 400h. */
+ ulong host_global_regs;
+#define DWC_OTG_HOST_GLOBAL_REG_OFFSET 0x400
+
+ /* Host Port 0 Control and Status Register */
+ ulong hprt0;
+#define DWC_OTG_HOST_PORT_REGS_OFFSET 0x440
+
+ /* Host Channel Specific Registers at offsets 500h-5FCh. */
+ ulong hc_regs[MAX_EPS_CHANNELS];
+#define DWC_OTG_HOST_CHAN_REGS_OFFSET 0x500
+#define DWC_OTG_CHAN_REGS_OFFSET 0x20
+
+ /* Host configuration information */
+ /* Number of Host Channels (range: 1-16) */
+ u8 num_host_channels;
+ /* Periodic EPs supported (0: no, 1: yes) */
+ u8 perio_eps_supported;
+ /* Periodic Tx FIFO Size (Only 1 host periodic Tx FIFO) */
+ u16 perio_tx_fifo_size;
+};
+#endif
--
1.7.0.4
^ permalink raw reply related
* [PATCH v16 02/10]USB/ppc4xx-Add-Synopsys-DWC-OTG-driver-framework
From: Rupjyoti Sarmah @ 2012-05-03 12:27 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel; +Cc: rsarmah
Platform probing is in apmppc.c.
Driver parameter and parameter checking are in param.c.
Signed-off-by: Rupjyoti Sarmah <rsarmah@apm.com>
Signed-off-by: Tirumala R Marri <tmarri@apm.com>
Signed-off-by: Fushen Chen <fchen@apm.com>
Signed-off-by: Mark Miesfeld <mmiesfeld@apm.com>
---
drivers/usb/dwc/apmppc.c | 350 ++++++++++++++++++++++++++++++++++++++++++++++
drivers/usb/dwc/driver.h | 76 ++++++++++
drivers/usb/dwc/param.c | 180 ++++++++++++++++++++++++
3 files changed, 606 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/dwc/apmppc.c
create mode 100644 drivers/usb/dwc/driver.h
create mode 100644 drivers/usb/dwc/param.c
diff --git a/drivers/usb/dwc/apmppc.c b/drivers/usb/dwc/apmppc.c
new file mode 100644
index 0000000..9b444dc
--- /dev/null
+++ b/drivers/usb/dwc/apmppc.c
@@ -0,0 +1,350 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/*
+ * The dwc_otg module provides the initialization and cleanup entry
+ * points for the dwcotg driver. This module will be dynamically installed
+ * after Linux is booted using the insmod command. When the module is
+ * installed, the dwc_otg_driver_init function is called. When the module is
+ * removed (using rmmod), the dwc_otg_driver_cleanup function is called.
+ *
+ * This module also defines a data structure for the dwc_otg driver, which is
+ * used in conjunction with the standard device structure. These
+ * structures allow the OTG driver to comply with the standard Linux driver
+ * model in which devices and drivers are registered with a bus driver. This
+ * has the benefit that Linux can expose attributes of the driver and device
+ * in its special sysfs file system. Users can then read or write files in
+ * this file system to perform diagnostics on the driver components or the
+ * device.
+ */
+#include <linux/module.h>
+
+#include <linux/of_platform.h>
+
+#include "driver.h"
+
+#define DWC_DRIVER_VERSION "1.05"
+#define DWC_DRIVER_DESC "HS OTG USB Controller driver"
+static const char dwc_driver_name[] = "dwc_otg";
+
+static irqreturn_t dwc_otg_common_irq(int _irq, void *dev)
+{
+ struct dwc_otg_device *dwc_dev = dev;
+ int retval;
+ struct dwc_hcd *dwc_hcd;
+
+ dwc_hcd = dwc_dev->hcd;
+ spin_lock(&dwc_hcd->lock);
+ retval = dwc_otg_handle_common_intr(dwc_dev->core_if);
+ spin_unlock(&dwc_hcd->lock);
+ return IRQ_RETVAL(retval);
+}
+
+static irqreturn_t dwc_otg_externalchgpump_irq(int _irq, void *dev)
+{
+ struct dwc_otg_device *dwc_dev = dev;
+
+ if (dwc_otg_is_host_mode(dwc_dev->core_if)) {
+ struct dwc_hcd *dwc_hcd;
+ u32 hprt0 = 0;
+
+ dwc_hcd = dwc_dev->hcd;
+ spin_lock(&dwc_hcd->lock);
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(dwc_dev->core_if->host_if->hprt0, 0, hprt0);
+ spin_unlock(&dwc_hcd->lock);
+ } else {
+ /* Device mode - This int is n/a for device mode */
+ dev_dbg(dev, "DeviceMode: OTG OverCurrent Detected\n");
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int __devexit dwc_otg_driver_remove(struct platform_device *ofdev)
+{
+ struct device *dev = &ofdev->dev;
+ struct dwc_otg_device *dwc_dev = dev_get_drvdata(dev);
+
+ /* Memory allocation for dwc_otg_device may have failed. */
+ if (!dwc_dev)
+ return 0;
+
+ /* Free the IRQ */
+ free_irq(dwc_dev->irq, dwc_dev);
+ /* Free external charge pump irq */
+ free_irq(dwc_dev->hcd->cp_irq, dwc_dev);
+
+ if (dwc_dev->hcd)
+ dwc_otg_hcd_remove(dev);
+
+ if (dwc_dev->pcd)
+ dwc_otg_pcd_remove(dev);
+
+ if (dwc_dev->core_if)
+ dwc_otg_cil_remove(dwc_dev->core_if);
+
+ /* Return the memory. */
+ if (dwc_dev->base)
+ iounmap(dwc_dev->base);
+
+ if (dwc_dev->phys_addr)
+ release_mem_region(dwc_dev->phys_addr, dwc_dev->base_len);
+
+ otg_put_transceiver(dwc_dev->core_if->xceiv);
+ dwc_dev->core_if->xceiv = NULL;
+
+ kfree(dwc_dev);
+
+ /* Clear the drvdata pointer. */
+ dev_set_drvdata(dev, NULL);
+ return 0;
+}
+
+static int __devinit dwc_otg_driver_probe(struct platform_device *ofdev)
+{
+ int retval;
+ struct dwc_otg_device *dwc_dev;
+ struct device *dev = &ofdev->dev;
+ struct resource res;
+ ulong gusbcfg_addr;
+ u32 usbcfg = 0;
+
+ dwc_dev = kzalloc(sizeof(*dwc_dev), GFP_KERNEL);
+ if (!dwc_dev) {
+ dev_err(dev, "kmalloc of dwc_otg_device failed\n");
+ retval = -ENOMEM;
+ goto fail_dwc_dev;
+ }
+
+ /* Retrieve the memory and IRQ resources. */
+ dwc_dev->irq = irq_of_parse_and_map(ofdev->dev.of_node, 0);
+ if (dwc_dev->irq == NO_IRQ) {
+ dev_err(dev, "no device irq\n");
+ retval = -ENODEV;
+ goto fail_of_irq;
+ }
+
+ if (of_address_to_resource(ofdev->dev.of_node, 0, &res)) {
+ dev_err(dev, "%s: Can't get USB-OTG register address\n",
+ __func__);
+ retval = -ENOMEM;
+ goto fail_of_irq;
+ }
+
+ dwc_dev->phys_addr = res.start;
+ dwc_dev->base_len = res.end - res.start + 1;
+ if (!request_mem_region(dwc_dev->phys_addr,
+ dwc_dev->base_len, dwc_driver_name)) {
+ dev_err(dev, "request_mem_region failed\n");
+ retval = -EBUSY;
+ goto fail_of_irq;
+ }
+
+ /* Map the DWC_otg Core memory into virtual address space. */
+ dwc_dev->base = ioremap(dwc_dev->phys_addr, dwc_dev->base_len);
+ if (!dwc_dev->base) {
+ dev_err(dev, "ioremap() failed\n");
+ retval = -ENOMEM;
+ goto fail_ioremap;
+ }
+ dev_dbg(dev, "mapped base=0x%08x\n", (__force u32)dwc_dev->base);
+
+ /*
+ * Initialize driver data to point to the global DWC_otg
+ * Device structure.
+ */
+ dev_set_drvdata(dev, dwc_dev);
+
+ dwc_dev->core_if =
+ dwc_otg_cil_init(dwc_dev->base, &dwc_otg_module_params);
+ if (!dwc_dev->core_if) {
+ dev_err(dev, "CIL initialization failed!\n");
+ retval = -ENOMEM;
+ goto fail_cil_init;
+ }
+
+ /*
+ * Validate parameter values after dwc_otg_cil_init.
+ */
+ if (check_parameters(dwc_dev->core_if)) {
+ retval = -EINVAL;
+ goto fail_check_param;
+ }
+
+ usb_nop_xceiv_register();
+ dwc_dev->core_if->xceiv = otg_get_transceiver();
+ if (!dwc_dev->core_if->xceiv) {
+ retval = -ENODEV;
+ goto fail_xceiv;
+ }
+ dwc_set_feature(dwc_dev->core_if);
+
+ /* Initialize the DWC_otg core. */
+ dwc_otg_core_init(dwc_dev->core_if);
+
+ /*
+ * Disable the global interrupt until all the interrupt
+ * handlers are installed.
+ */
+ spin_lock(&dwc_dev->hcd->lock);
+ dwc_otg_disable_global_interrupts(dwc_dev->core_if);
+ spin_unlock(&dwc_dev->hcd->lock);
+
+ /*
+ * Install the interrupt handler for the common interrupts before
+ * enabling common interrupts in core_init below.
+ */
+ retval = request_irq(dwc_dev->irq, dwc_otg_common_irq,
+ IRQF_SHARED, "dwc_otg", dwc_dev);
+ if (retval) {
+ dev_err(dev, "request of irq%d failed retval: %d\n",
+ dwc_dev->irq, retval);
+ retval = -EBUSY;
+ goto fail_req_irq;
+ } else {
+ dwc_dev->common_irq_installed = 1;
+ }
+
+ if (!dwc_has_feature(dwc_dev->core_if, DWC_HOST_ONLY)) {
+ /* Initialize the PCD */
+ retval = dwc_otg_pcd_init(dev);
+ if (retval) {
+ dev_err(dev, "dwc_otg_pcd_init failed\n");
+ dwc_dev->pcd = NULL;
+ goto fail_req_irq;
+ }
+ }
+
+ gusbcfg_addr = (ulong) (dwc_dev->core_if->core_global_regs)
+ + DWC_GUSBCFG;
+ if (!dwc_has_feature(dwc_dev->core_if, DWC_DEVICE_ONLY)) {
+ /* Initialize the HCD and force_host_mode */
+ usbcfg = dwc_reg_read(gusbcfg_addr, 0);
+ usbcfg |= DWC_USBCFG_FRC_HST_MODE;
+ usbcfg &= ~DWC_USBCFG_FRC_DEV_MODE;
+ dwc_reg_write(gusbcfg_addr, 0, usbcfg);
+
+ retval = dwc_otg_hcd_init(dev, dwc_dev);
+ if (retval) {
+ dev_err(dev, "dwc_otg_hcd_init failed\n");
+ dwc_dev->hcd = NULL;
+ goto fail_hcd;
+ }
+ /* configure chargepump interrupt */
+ dwc_dev->hcd->cp_irq = irq_of_parse_and_map(ofdev->dev.of_node,
+ 3);
+ if (dwc_dev->hcd->cp_irq != -ENXIO) {
+ retval = request_irq(dwc_dev->hcd->cp_irq,
+ dwc_otg_externalchgpump_irq,
+ IRQF_SHARED,
+ "dwc_otg_ext_chg_pump", dwc_dev);
+ if (retval) {
+ dev_err(dev,
+ "request of irq failed retval: %d\n",
+ retval);
+ retval = -EBUSY;
+ goto fail_hcd;
+ } else {
+ dev_dbg(dev, "%s: ExtChgPump Detection "
+ "IRQ registered\n", dwc_driver_name);
+ }
+ }
+ }
+ /*
+ * Enable the global interrupt after all the interrupt
+ * handlers are installed.
+ */
+ dwc_otg_enable_global_interrupts(dwc_dev->core_if);
+ return 0;
+fail_hcd:
+ free_irq(dwc_dev->irq, dwc_dev);
+ if (!dwc_has_feature(dwc_dev->core_if, DWC_HOST_ONLY)) {
+ if (dwc_dev->pcd)
+ dwc_otg_pcd_remove(dev);
+ }
+fail_req_irq:
+ otg_put_transceiver(dwc_dev->core_if->xceiv);
+fail_xceiv:
+ usb_nop_xceiv_unregister();
+fail_check_param:
+ dwc_otg_cil_remove(dwc_dev->core_if);
+fail_cil_init:
+ dev_set_drvdata(dev, NULL);
+ iounmap(dwc_dev->base);
+fail_ioremap:
+ release_mem_region(dwc_dev->phys_addr, dwc_dev->base_len);
+fail_of_irq:
+ kfree(dwc_dev);
+fail_dwc_dev:
+ return retval;
+}
+
+static const struct of_device_id dwc_otg_match[] = {
+ {.compatible = "amcc,dwc-otg",},
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, dwc_otg_match);
+
+static struct platform_driver dwc_otg_driver = {
+ .probe = dwc_otg_driver_probe,
+ .remove = __devexit_p(dwc_otg_driver_remove),
+ .driver = {
+ .name = "dwc_otg",
+ .owner = THIS_MODULE,
+ .of_match_table = dwc_otg_match,
+ },
+};
+
+static int __init dwc_otg_driver_init(void)
+{
+
+ return platform_driver_register(&dwc_otg_driver);
+}
+
+module_init(dwc_otg_driver_init);
+
+static void __exit dwc_otg_driver_cleanup(void)
+{
+ platform_driver_unregister(&dwc_otg_driver);
+}
+
+module_exit(dwc_otg_driver_cleanup);
+
+MODULE_DESCRIPTION(DWC_DRIVER_DESC);
+MODULE_AUTHOR("Mark Miesfeld <mmiesfeld@apm.com");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/dwc/driver.h b/drivers/usb/dwc/driver.h
new file mode 100644
index 0000000..a86532b
--- /dev/null
+++ b/drivers/usb/dwc/driver.h
@@ -0,0 +1,76 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#if !defined(__DWC_OTG_DRIVER_H__)
+#define __DWC_OTG_DRIVER_H__
+
+/*
+ * This file contains the interface to the Linux driver.
+ */
+#include "cil.h"
+
+/*
+ * This structure is a wrapper that encapsulates the driver components used to
+ * manage a single DWC_otg controller.
+ */
+struct dwc_otg_device {
+ /* Base address returned from ioremap() */
+ __iomem void *base;
+
+ /* Pointer to the core interface structure. */
+ struct core_if *core_if;
+
+ /* Pointer to the PCD structure. */
+ struct dwc_pcd *pcd;
+
+ /* Pointer to the HCD structure. */
+ struct dwc_hcd *hcd;
+
+ /* Flag to indicate whether the common IRQ handler is installed. */
+ u8 common_irq_installed;
+
+ /* Interrupt request number. */
+ unsigned int irq;
+
+ /*
+ * Physical address of Control and Status registers, used by
+ * release_mem_region().
+ */
+ resource_size_t phys_addr;
+
+ /* Length of memory region, used by release_mem_region(). */
+ unsigned long base_len;
+};
+#endif
diff --git a/drivers/usb/dwc/param.c b/drivers/usb/dwc/param.c
new file mode 100644
index 0000000..55c17b1
--- /dev/null
+++ b/drivers/usb/dwc/param.c
@@ -0,0 +1,180 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/*
+ * This file provides dwc_otg driver parameter and parameter checking.
+ */
+
+#include "cil.h"
+#include "linux/module.h"
+/*
+ * Encapsulate the module parameter settings
+ */
+struct core_params dwc_otg_module_params = {
+ .otg_cap = -1,
+ .dma_enable = -1,
+ .dma_burst_size = -1,
+ .speed = -1,
+ .host_support_fs_ls_low_power = -1,
+ .host_ls_low_power_phy_clk = -1,
+ .enable_dynamic_fifo = -1,
+ .dev_rx_fifo_size = -1,
+ .dev_nperio_tx_fifo_size = -1,
+ .dev_perio_tx_fifo_size = {-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1}, /* 15 */
+ .host_rx_fifo_size = -1,
+ .host_nperio_tx_fifo_size = -1,
+ .host_perio_tx_fifo_size = -1,
+ .max_transfer_size = -1,
+ .max_packet_count = -1,
+ .host_channels = -1,
+ .dev_endpoints = -1,
+ .phy_type = -1,
+ .phy_utmi_width = -1,
+ .phy_ulpi_ddr = -1,
+ .phy_ulpi_ext_vbus = -1,
+ .i2c_enable = -1,
+ .ulpi_fs_ls = -1,
+ .ts_dline = -1,
+ .en_multiple_tx_fifo = -1,
+ .dev_tx_fifo_size = {-1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1}, /* 15 */
+ .thr_ctl = -1,
+ .tx_thr_length = -1,
+ .rx_thr_length = -1,
+};
+
+/**
+ * Checks that parameter settings for the periodic Tx FIFO sizes are correct
+ * according to the hardware configuration. Sets the size to the hardware
+ * configuration if an incorrect size is detected.
+ */
+static int set_valid_perio_tx_fifo_sizes(struct core_if *core_if)
+{
+ ulong regs = (u32) core_if->core_global_regs;
+ u32 *param_size = &dwc_otg_module_params.dev_perio_tx_fifo_size[0];
+ u32 i, size;
+
+ for (i = 0; i < MAX_PERIO_FIFOS; i++, param_size++) {
+ size = dwc_reg_read(regs, DWC_DPTX_FSIZ_DIPTXF(i));
+ *param_size = size;
+ }
+ return 0;
+}
+
+/**
+ * Checks that parameter settings for the Tx FIFO sizes are correct according to
+ * the hardware configuration. Sets the size to the hardware configuration if
+ * an incorrect size is detected.
+ */
+static int set_valid_tx_fifo_sizes(struct core_if *core_if)
+{
+ ulong regs = (u32) core_if->core_global_regs;
+ u32 *param_size = &dwc_otg_module_params.dev_tx_fifo_size[0];
+ u32 i, size;
+
+ for (i = 0; i < MAX_TX_FIFOS; i++, param_size) {
+ size = dwc_reg_read(regs, DWC_DPTX_FSIZ_DIPTXF(i));
+ *param_size = size;
+ }
+ return 0;
+}
+
+/**
+ * This function is called during module intialization to verify that
+ * the module parameters are in a valid state.
+ */
+int __devinit check_parameters(struct core_if *core_if)
+{
+ /* Default values */
+ dwc_otg_module_params.otg_cap = dwc_param_otg_cap_default;
+ dwc_otg_module_params.dma_enable = dwc_param_dma_enable_default;
+ dwc_otg_module_params.speed = dwc_param_speed_default;
+ dwc_otg_module_params.host_support_fs_ls_low_power =
+ dwc_param_host_support_fs_ls_low_power_default;
+ dwc_otg_module_params.host_ls_low_power_phy_clk =
+ dwc_param_host_ls_low_power_phy_clk_default;
+ dwc_otg_module_params.phy_type = dwc_param_phy_type_default;
+ dwc_otg_module_params.phy_ulpi_ddr = dwc_param_phy_ulpi_ddr_default;
+ dwc_otg_module_params.phy_ulpi_ext_vbus =
+ dwc_param_phy_ulpi_ext_vbus_default;
+ dwc_otg_module_params.i2c_enable = dwc_param_i2c_enable_default;
+ dwc_otg_module_params.ulpi_fs_ls = dwc_param_ulpi_fs_ls_default;
+ dwc_otg_module_params.ts_dline = dwc_param_ts_dline_default;
+
+ dwc_otg_module_params.dma_burst_size = dwc_param_dma_burst_size_default;
+ dwc_otg_module_params.phy_utmi_width = dwc_param_phy_utmi_width_default;
+ dwc_otg_module_params.thr_ctl = dwc_param_thr_ctl_default;
+ dwc_otg_module_params.tx_thr_length = dwc_param_tx_thr_length_default;
+ dwc_otg_module_params.rx_thr_length = dwc_param_rx_thr_length_default;
+
+ /*
+ * Hardware configurations of the OTG core.
+ */
+ dwc_otg_module_params.enable_dynamic_fifo =
+ DWC_HWCFG2_DYN_FIFO_RD(core_if->hwcfg2);
+ dwc_otg_module_params.dev_rx_fifo_size =
+ dwc_reg_read(core_if->core_global_regs, DWC_GRXFSIZ);
+ dwc_otg_module_params.dev_nperio_tx_fifo_size =
+ dwc_reg_read(core_if->core_global_regs, DWC_GNPTXFSIZ) >> 16;
+
+ dwc_otg_module_params.host_rx_fifo_size =
+ dwc_reg_read(core_if->core_global_regs, DWC_GRXFSIZ);
+ dwc_otg_module_params.host_nperio_tx_fifo_size =
+ dwc_reg_read(core_if->core_global_regs, DWC_GNPTXFSIZ) >> 16;
+ dwc_otg_module_params.host_perio_tx_fifo_size =
+ dwc_reg_read(core_if->core_global_regs, DWC_HPTXFSIZ) >> 16;
+ dwc_otg_module_params.max_transfer_size =
+ (1 << (DWC_HWCFG3_XFERSIZE_CTR_WIDTH_RD(core_if->hwcfg3) + 11))
+ - 1;
+ dwc_otg_module_params.max_packet_count =
+ (1 << (DWC_HWCFG3_PKTSIZE_CTR_WIDTH_RD(core_if->hwcfg3) + 4))
+ - 1;
+
+ dwc_otg_module_params.host_channels =
+ DWC_HWCFG2_NO_HST_CHAN_RD(core_if->hwcfg2) + 1;
+ dwc_otg_module_params.dev_endpoints =
+ DWC_HWCFG2_NO_DEV_EP_RD(core_if->hwcfg2);
+ dwc_otg_module_params.en_multiple_tx_fifo =
+ (DWC_HWCFG4_DED_FIFO_ENA_RD(core_if->hwcfg4) == 0)
+ ? 0 : 1, 0;
+ set_valid_perio_tx_fifo_sizes(core_if);
+ set_valid_tx_fifo_sizes(core_if);
+
+ return 0;
+}
+
+module_param_named(dma_enable, dwc_otg_module_params.dma_enable, bool, 0444);
+MODULE_PARM_DESC(dma_enable, "DMA Mode 0=Slave 1=DMA enabled");
--
1.7.0.4
^ permalink raw reply related
* [PATCH v16 10/10]USB/ppc4xx:Synopsys DWC OTG driver enable gadget support
From: Rupjyoti Sarmah @ 2012-05-03 12:43 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel; +Cc: rsarmah
Enable gadget support
Signed-off-by: Rupjyoti Sarmah <rsarmah@apm.com>
Signed-off-by: Tirumala R Marri <tmarri@apm.com>
Signed-off-by: Fushen Chen <fchen@apm.com>
Signed-off-by: Mark Miesfeld <mmiesfeld@apm.com>
---
drivers/usb/gadget/Kconfig | 11 +++++++++++
drivers/usb/gadget/gadget_chips.h | 9 ++++++++-
2 files changed, 19 insertions(+), 1 deletions(-)
diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
index 2633f75..4bca4dc 100644
--- a/drivers/usb/gadget/Kconfig
+++ b/drivers/usb/gadget/Kconfig
@@ -329,6 +329,17 @@ config USB_GADGET_MUSB_HDRC
This OTG-capable silicon IP is used in dual designs including
the TI DaVinci, OMAP 243x, OMAP 343x, TUSB 6010, and ADI Blackfin
+# dwc_otg builds in ../dwc along with host support
+config USB_GADGET_DWC_HDRC
+ boolean "DesignWare USB Peripheral"
+ depends on DWC_OTG_MODE || DWC_DEVICE_ONLY
+ select USB_GADGET_DUALSPEED
+ select USB_GADGET_SELECTED
+ select USB_GADGET_DWC_OTG
+ help
+ This OTG-capable Designware USB IP which has host and device
+ modes.
+
config USB_M66592
tristate "Renesas M66592 USB Peripheral Controller"
select USB_GADGET_DUALSPEED
diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h
index a8855d0..ab1ea6e 100644
--- a/drivers/usb/gadget/gadget_chips.h
+++ b/drivers/usb/gadget/gadget_chips.h
@@ -51,6 +51,12 @@
#define gadget_is_s3c_hsotg(g) (!strcmp("s3c-hsotg", (g)->name))
#define gadget_is_s3c_hsudc(g) (!strcmp("s3c-hsudc", (g)->name))
+#if defined(CONFIG_DWC_OTG_MODE) || defined(CONFIG_DWC_DEVICE_ONLY)
+#define gadget_is_dwc_otg_pcd(g) (!strcmp("dwc_otg_pcd", (g)->name))
+#else
+#define gadget_is_dwc_otg_pcd(g) 0
+#endif
+
/**
* usb_gadget_controller_number - support bcdDevice id convention
* @gadget: the controller being driven
@@ -118,11 +124,12 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget)
return 0x31;
else if (gadget_is_dwc3(gadget))
return 0x32;
+ else if (gadget_is_dwc_otg_pcd(gadget))
+ return 0x33;
return -ENOENT;
}
-
/**
* gadget_supports_altsettings - return true if altsettings work
* @gadget: the gadget in question
--
1.7.0.4
^ permalink raw reply related
* [PATCH v16 09/10]USB/ppc4xx: Add Synopsys DWC OTG driver kernel configuration and Makefile
From: Rupjyoti Sarmah @ 2012-05-03 12:41 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel; +Cc: rsarmah
Add Synopsys DesignWare HS USB OTG driver kernel configuration.
Synopsys OTG driver may operate in host only, device only, or OTG mode.
The driver also allows user configure the core to use its internal DMA
or Slave (PIO) mode.
Signed-off-by: Rupjyoti Sarmah <rsarmah@apm.com>
Signed-off-by: Tirumala R Marri <tmarri@apm.com>
Signed-off-by: Fushen Chen <fchen@apm.com>
Signed-off-by: Mark Miesfeld <mmiesfeld@apm.com>
---
drivers/Makefile | 1 +
drivers/usb/Kconfig | 2 +
drivers/usb/dwc/Kconfig | 84 ++++++++++++++++++++++++++++++++++++++++++++++
drivers/usb/dwc/Makefile | 19 ++++++++++
4 files changed, 106 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/dwc/Kconfig
create mode 100644 drivers/usb/dwc/Makefile
diff --git a/drivers/Makefile b/drivers/Makefile
index 262b19d..bae5eb9 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -70,6 +70,7 @@ obj-$(CONFIG_PARIDE) += block/paride/
obj-$(CONFIG_TC) += tc/
obj-$(CONFIG_UWB) += uwb/
obj-$(CONFIG_USB_OTG_UTILS) += usb/
+obj-$(CONFIG_USB_DWC_OTG) += usb/dwc/
obj-$(CONFIG_USB) += usb/
obj-$(CONFIG_PCI) += usb/
obj-$(CONFIG_USB_GADGET) += usb/
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index cbd8f5f..6812758 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -133,6 +133,8 @@ source "drivers/usb/host/Kconfig"
source "drivers/usb/musb/Kconfig"
+source "drivers/usb/dwc/Kconfig"
+
source "drivers/usb/renesas_usbhs/Kconfig"
source "drivers/usb/class/Kconfig"
diff --git a/drivers/usb/dwc/Kconfig b/drivers/usb/dwc/Kconfig
new file mode 100644
index 0000000..eafc5ed
--- /dev/null
+++ b/drivers/usb/dwc/Kconfig
@@ -0,0 +1,84 @@
+#
+# USB Dual Role (OTG-ready) Controller Drivers
+# for silicon based on Synopsys DesignWare IP
+#
+
+comment "Enable Host or Gadget support for DesignWare OTG controller"
+ depends on !USB && USB_GADGET=n
+
+config USB_DWC_OTG
+ tristate "Synopsys DWC OTG Controller"
+ depends on USB || USB_GADGET
+ select NOP_USB_XCEIV
+ select USB_OTG_UTILS
+ default USB_GADGET
+ help
+ This driver provides USB Device Controller support for the
+ Synopsys DesignWare USB OTG Core used on the AppliedMicro PowerPC SoC.
+
+config DWC_DEBUG
+ bool "Enable DWC Debugging"
+ depends on USB_DWC_OTG
+ default n
+ help
+ Enable DWC driver debugging
+
+choice
+ prompt "DWC Mode Selection"
+ depends on USB_DWC_OTG
+ default DWC_HOST_ONLY
+ help
+ Select the DWC Core in OTG, Host only, or Device only mode.
+
+config DWC_HOST_ONLY
+ bool "DWC Host Only Mode"
+
+config DWC_OTG_MODE
+ bool "DWC OTG Mode"
+ select USB_GADGET_SELECTED
+
+config DWC_DEVICE_ONLY
+ bool "DWC Device Only Mode"
+ select USB_GADGET_SELECTED
+
+endchoice
+
+# enable peripheral support (including with OTG)
+choice
+ prompt "DWC DMA/SlaveMode Selection"
+ depends on USB_DWC_OTG
+ default DWC_DMA_MODE
+ help
+ Select the DWC DMA or Slave Mode.
+ DMA mode uses the DWC core internal DMA engines.
+ Slave mode uses the processor PIO to tranfer data.
+ In Slave mode, processor's DMA channels can be used if available.
+
+config DWC_SLAVE
+ bool "DWC Slave Mode"
+
+config DWC_DMA_MODE
+ bool "DWC DMA Mode"
+
+endchoice
+
+config DWC_OTG_REG_LE
+ bool "DWC Little Endian Register"
+ depends on USB_DWC_OTG
+ default y
+ help
+ OTG core register access is Little-Endian.
+
+config DWC_OTG_FIFO_LE
+ bool "DWC FIFO Little Endian"
+ depends on USB_DWC_OTG
+ default n
+ help
+ OTG core FIFO access is Little-Endian.
+
+config DWC_LIMITED_XFER_SIZE
+ bool "DWC Endpoint Limited Xfer Size"
+ depends on USB_GADGET_DWC_HDRC
+ default n
+ help
+ Bit fields in the Device EP Transfer Size Register is 11 bits.
diff --git a/drivers/usb/dwc/Makefile b/drivers/usb/dwc/Makefile
new file mode 100644
index 0000000..4102add
--- /dev/null
+++ b/drivers/usb/dwc/Makefile
@@ -0,0 +1,19 @@
+#
+# OTG infrastructure and transceiver drivers
+#
+obj-$(CONFIG_USB_DWC_OTG) += dwc.o
+
+dwc-objs := cil.o cil_intr.o param.o
+
+ifeq ($(CONFIG_4xx_SOC),y)
+dwc-objs += apmppc.o
+endif
+
+ifneq ($(CONFIG_DWC_DEVICE_ONLY),y)
+dwc-objs += hcd.o hcd_intr.o \
+ hcd_queue.o
+endif
+
+ifneq ($(CONFIG_DWC_HOST_ONLY),y)
+dwc-objs += pcd.o pcd_intr.o
+endif
--
1.7.0.4
^ permalink raw reply related
* [PATCH v16 08/10]USB/ppc4xx: Add Synopsys DWC OTG PCD interrupt function
From: Rupjyoti Sarmah @ 2012-05-03 12:39 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel; +Cc: rsarmah
Implements the DWC OTG PCD Interrupt Service routine.
Signed-off-by: Rupjyoti Sarmah <rsarmah@apm.com>
Signed-off-by: Tirumala R Marri <tmarri@apm.com>
Signed-off-by: Fushen Chen <fchen@apm.com>
Signed-off-by: Mark Miesfeld <mmiesfeld@apm.com>
---
drivers/usb/dwc/pcd_intr.c | 2260 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 2260 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/dwc/pcd_intr.c
diff --git a/drivers/usb/dwc/pcd_intr.c b/drivers/usb/dwc/pcd_intr.c
new file mode 100644
index 0000000..0ab87ee
--- /dev/null
+++ b/drivers/usb/dwc/pcd_intr.c
@@ -0,0 +1,2260 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "driver.h"
+#include "pcd.h"
+
+/**
+ * This function returns pointer to in ep struct with number num
+ */
+static struct pcd_ep *get_in_ep(struct dwc_pcd *pcd, u32 num)
+{
+ if (num == 0) {
+ return &pcd->ep0;
+ } else {
+ u32 i;
+ int num_in_eps = GET_CORE_IF(pcd)->dev_if->num_in_eps;
+
+ for (i = 0; i < num_in_eps; ++i) {
+ if (pcd->in_ep[i].dwc_ep.num == num)
+ return &pcd->in_ep[i];
+ }
+ }
+ return NULL;
+}
+
+/**
+ * This function returns pointer to out ep struct with number num
+ */
+static struct pcd_ep *get_out_ep(struct dwc_pcd *pcd, u32 num)
+{
+ if (num == 0) {
+ return &pcd->ep0;
+ } else {
+ u32 i;
+ int num_out_eps = GET_CORE_IF(pcd)->dev_if->num_out_eps;
+
+ for (i = 0; i < num_out_eps; ++i) {
+ if (pcd->out_ep[i].dwc_ep.num == num)
+ return &pcd->out_ep[i];
+ }
+ }
+ return NULL;
+}
+
+/**
+ * This functions gets a pointer to an EP from the wIndex address
+ * value of the control request.
+ */
+static struct pcd_ep *get_ep_by_addr(struct dwc_pcd *pcd, u16 index)
+{
+ struct pcd_ep *ep;
+
+ if (!(index & USB_ENDPOINT_NUMBER_MASK))
+ return &pcd->ep0;
+
+ list_for_each_entry(ep, &pcd->gadget.ep_list, ep.ep_list) {
+ u8 bEndpointAddress;
+
+ if (!ep->desc)
+ continue;
+
+ bEndpointAddress = ep->desc->bEndpointAddress;
+ if ((index ^ bEndpointAddress) & USB_DIR_IN)
+ continue;
+
+ if ((index & 0x0f) == (bEndpointAddress & 0x0f))
+ return ep;
+ }
+ return NULL;
+}
+
+/**
+ * This function checks the EP request queue, if the queue is not
+ * empty the next request is started.
+ */
+void dwc_start_next_request(struct pcd_ep *ep)
+{
+ if (!list_empty(&ep->queue)) {
+ struct pcd_request *req;
+
+ req = list_entry(ep->queue.next, struct pcd_request, queue);
+
+ /* Setup and start the Transfer */
+ ep->dwc_ep.start_xfer_buff = req->req.buf;
+ ep->dwc_ep.xfer_buff = req->req.buf;
+ ep->dwc_ep.xfer_len = req->req.length;
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.dma_addr = req->req.dma;
+ ep->dwc_ep.sent_zlp = 0;
+ ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;
+
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * When a new transfer will be started, mark this
+ * endpoint as active. This way it will be blocked
+ * for further transfers, until the current transfer
+ * is finished.
+ */
+ if (dwc_has_feature(GET_CORE_IF(ep->pcd), DWC_LIMITED_XFER))
+ ep->dwc_ep.active = 1;
+
+ dwc_otg_ep_start_transfer(GET_CORE_IF(ep->pcd), &ep->dwc_ep);
+ }
+}
+
+/**
+ * This function handles the SOF Interrupts. At this time the SOF
+ * Interrupt is disabled.
+ */
+static int dwc_otg_pcd_handle_sof_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ u32 gintsts;
+
+ /* Clear interrupt */
+ gintsts = 0;
+ gintsts |= DWC_INTMSK_STRT_OF_FRM;
+ dwc_reg_write((core_if->core_global_regs), DWC_GINTSTS, gintsts);
+ return 1;
+}
+
+/**
+ * This function reads the 8 bytes of the setup packet from the Rx FIFO into the
+ * destination buffer. It is called from the Rx Status Queue Level (RxStsQLvl)
+ * interrupt routine when a SETUP packet has been received in Slave mode.
+ */
+static void dwc_otg_read_setup_packet(struct core_if *core_if, u32 * dest)
+{
+ dest[0] = dwc_read_fifo32(core_if->data_fifo[0]);
+ dest[1] = dwc_read_fifo32(core_if->data_fifo[0]);
+}
+
+/**
+ * This function handles the Rx Status Queue Level Interrupt, which
+ * indicates that there is a least one packet in the Rx FIFO. The
+ * packets are moved from the FIFO to memory, where they will be
+ * processed when the Endpoint Interrupt Register indicates Transfer
+ * Complete or SETUP Phase Done.
+ *
+ * Repeat the following until the Rx Status Queue is empty:
+ * -# Read the Receive Status Pop Register (GRXSTSP) to get Packet
+ * info
+ * -# If Receive FIFO is empty then skip to step Clear the interrupt
+ * and exit
+ * -# If SETUP Packet call dwc_otg_read_setup_packet to copy the
+ * SETUP data to the buffer
+ * -# If OUT Data Packet call dwc_otg_read_packet to copy the data
+ * to the destination buffer
+ */
+static int dwc_otg_pcd_handle_rx_status_q_level_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ ulong global_regs = core_if->core_global_regs;
+ u32 grxsts;
+ struct pcd_ep *ep;
+ u32 gintsts;
+
+ /* Disable the Rx Status Queue Level interrupt */
+ dwc_reg_modify(global_regs, DWC_GINTMSK, DWC_INTMSK_RXFIFO_NOT_EMPT, 0);
+
+ /* Get the Status from the top of the FIFO */
+ grxsts = dwc_reg_read(global_regs, DWC_GRXSTSP);
+
+ /* Get pointer to EP structure */
+ ep = get_out_ep(pcd, DWC_DM_RXSTS_CHAN_NUM_RD(grxsts));
+
+ switch (DWC_DM_RXSTS_PKT_STS_RD(grxsts)) {
+ case DWC_DSTS_GOUT_NAK:
+ break;
+ case DWC_STS_DATA_UPDT:
+ if ((grxsts & DWC_DM_RXSTS_BYTE_CNT) && ep->dwc_ep.xfer_buff) {
+ dwc_otg_read_packet(core_if, ep->dwc_ep.xfer_buff,
+ DWC_DM_RXSTS_BYTE_CNT_RD(grxsts));
+ ep->dwc_ep.xfer_count +=
+ DWC_DM_RXSTS_BYTE_CNT_RD(grxsts);
+ ep->dwc_ep.xfer_buff +=
+ DWC_DM_RXSTS_BYTE_CNT_RD(grxsts);
+ }
+ break;
+ case DWC_STS_XFER_COMP:
+ break;
+ case DWC_DSTS_SETUP_COMP:
+ break;
+ case DWC_DSTS_SETUP_UPDT:
+ dwc_otg_read_setup_packet(core_if, pcd->setup_pkt->d32);
+ ep->dwc_ep.xfer_count += DWC_DM_RXSTS_BYTE_CNT_RD(grxsts);
+ break;
+ default:
+ pr_err("RX_STS_Q Interrupt: Unknown status %d\n",
+ DWC_HM_RXSTS_PKT_STS_RD(grxsts));
+ break;
+ }
+
+ /* Enable the Rx Status Queue Level interrupt */
+ dwc_reg_modify(global_regs, DWC_GINTMSK, 0, DWC_INTMSK_RXFIFO_NOT_EMPT);
+
+ /* Clear interrupt */
+ dwc_reg_write(global_regs, DWC_GINTSTS, DWC_INTSTS_RXFIFO_NOT_EMPT);
+ return 1;
+}
+
+/**
+ * This function examines the Device IN Token Learning Queue to
+ * determine the EP number of the last IN token received. This
+ * implementation is for the Mass Storage device where there are only
+ * 2 IN EPs (Control-IN and BULK-IN).
+ *
+ * The EP numbers for the first six IN Tokens are in DTKNQR1 and there
+ * are 8 EP Numbers in each of the other possible DTKNQ Registers.
+ */
+static int get_ep_of_last_in_token(struct core_if *core_if)
+{
+ ulong regs = core_if->dev_if->dev_global_regs;
+ const u32 TOKEN_Q_DEPTH =
+ DWC_HWCFG2_DEV_TKN_Q_DEPTH_RD(core_if->hwcfg2);
+ /* Number of Token Queue Registers */
+ const int DTKNQ_REG_CNT = (TOKEN_Q_DEPTH + 7) / 8;
+ u32 dtknqr1 = 0;
+ u32 in_tkn_epnums[4];
+ int ndx;
+ u32 i;
+ u32 addr = regs + DWC_DTKNQR1;
+ int epnum = 0;
+
+ /* Read the DTKNQ Registers */
+ for (i = 0; i <= DTKNQ_REG_CNT; i++) {
+ in_tkn_epnums[i] = dwc_reg_read(addr, 0);
+
+ if (addr == (regs + DWC_DVBUSDIS))
+ addr = regs + DWC_DTKNQR3_DTHRCTL;
+ else
+ ++addr;
+ }
+
+ /* Copy the DTKNQR1 data to the bit field. */
+ dtknqr1 = in_tkn_epnums[0];
+
+ /* Get the EP numbers */
+ in_tkn_epnums[0] = DWC_DTKNQR1_EP_TKN_NO_RD(dtknqr1);
+ ndx = DWC_DTKNQR1_INT_TKN_Q_WR_PTR_RD(dtknqr1) - 1;
+
+ if (ndx == -1) {
+ /*
+ * Calculate the max queue position.
+ */
+ int cnt = TOKEN_Q_DEPTH;
+
+ if (TOKEN_Q_DEPTH <= 6)
+ cnt = TOKEN_Q_DEPTH - 1;
+ else if (TOKEN_Q_DEPTH <= 14)
+ cnt = TOKEN_Q_DEPTH - 7;
+ else if (TOKEN_Q_DEPTH <= 22)
+ cnt = TOKEN_Q_DEPTH - 15;
+ else
+ cnt = TOKEN_Q_DEPTH - 23;
+
+ epnum = (in_tkn_epnums[DTKNQ_REG_CNT - 1] >> (cnt * 4)) & 0xF;
+ } else {
+ if (ndx <= 5) {
+ epnum = (in_tkn_epnums[0] >> (ndx * 4)) & 0xF;
+ } else if (ndx <= 13) {
+ ndx -= 6;
+ epnum = (in_tkn_epnums[1] >> (ndx * 4)) & 0xF;
+ } else if (ndx <= 21) {
+ ndx -= 14;
+ epnum = (in_tkn_epnums[2] >> (ndx * 4)) & 0xF;
+ } else if (ndx <= 29) {
+ ndx -= 22;
+ epnum = (in_tkn_epnums[3] >> (ndx * 4)) & 0xF;
+ }
+ }
+
+ return epnum;
+}
+
+static inline int count_dwords(struct pcd_ep *ep, u32 len)
+{
+ if (len > ep->dwc_ep.maxpacket)
+ len = ep->dwc_ep.maxpacket;
+ return (len + 3) / 4;
+}
+
+/**
+ * This function writes a packet into the Tx FIFO associated with the EP. For
+ * non-periodic EPs the non-periodic Tx FIFO is written. For periodic EPs the
+ * periodic Tx FIFO associated with the EP is written with all packets for the
+ * next micro-frame.
+ *
+ * The buffer is padded to DWORD on a per packet basis in
+ * slave/dma mode if the MPS is not DWORD aligned. The last packet, if
+ * short, is also padded to a multiple of DWORD.
+ *
+ * ep->xfer_buff always starts DWORD aligned in memory and is a
+ * multiple of DWORD in length
+ *
+ * ep->xfer_len can be any number of bytes
+ *
+ * ep->xfer_count is a multiple of ep->maxpacket until the last packet
+ *
+ * FIFO access is DWORD
+ */
+static void dwc_otg_ep_write_packet(struct core_if *core_if, struct dwc_ep *ep,
+ int dma)
+{
+ u32 i;
+ u32 byte_count;
+ u32 dword_count;
+ u32 fifo;
+ u32 *data_buff = (u32 *) ep->xfer_buff;
+
+ if (ep->xfer_count >= ep->xfer_len)
+ return;
+
+ /* Find the byte length of the packet either short packet or MPS */
+ if ((ep->xfer_len - ep->xfer_count) < ep->maxpacket)
+ byte_count = ep->xfer_len - ep->xfer_count;
+ else
+ byte_count = ep->maxpacket;
+
+ /*
+ * Find the DWORD length, padded by extra bytes as neccessary if MPS
+ * is not a multiple of DWORD
+ */
+ dword_count = (byte_count + 3) / 4;
+
+ fifo = core_if->data_fifo[ep->num];
+
+ if (!dma)
+ for (i = 0; i < dword_count; i++, data_buff++)
+ dwc_write_fifo32(fifo, *data_buff);
+
+ ep->xfer_count += byte_count;
+ ep->xfer_buff += byte_count;
+ ep->dma_addr += byte_count;
+}
+
+/**
+ * This interrupt occurs when the non-periodic Tx FIFO is half-empty.
+ * The active request is checked for the next packet to be loaded into
+ * the non-periodic Tx FIFO.
+ */
+static int dwc_otg_pcd_handle_np_tx_fifo_empty_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ ulong global_regs = core_if->core_global_regs;
+ u32 txstatus = 0;
+ int epnum;
+ struct pcd_ep *ep;
+ u32 len;
+ int dwords;
+
+ /* Get the epnum from the IN Token Learning Queue. */
+ epnum = get_ep_of_last_in_token(core_if);
+ ep = get_in_ep(pcd, epnum);
+
+ txstatus = dwc_reg_read(global_regs, DWC_GNPTXSTS);
+
+ /*
+ * While there is space in the queue, space in the FIFO, and data to
+ * tranfer, write packets to the Tx FIFO
+ */
+ len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count;
+ dwords = count_dwords(ep, len);
+ while ((DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(txstatus) > 0) &&
+ (DWC_GNPTXSTS_NPTXFSPCAVAIL_RD(txstatus) > dwords) &&
+ ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len) {
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * When a new transfer will be started, mark this
+ * endpoint as active. This way it will be blocked
+ * for further transfers, until the current transfer
+ * is finished.
+ */
+ if (dwc_has_feature(core_if, DWC_LIMITED_XFER))
+ ep->dwc_ep.active = 1;
+
+ dwc_otg_ep_write_packet(core_if, &ep->dwc_ep, 0);
+ len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count;
+ dwords = count_dwords(ep, len);
+ txstatus = dwc_reg_read(global_regs, DWC_GNPTXSTS);
+ }
+
+ /* Clear nptxfempty interrupt */
+ dwc_reg_write(global_regs, DWC_GINTSTS, DWC_INTMSK_RXFIFO_NOT_EMPT);
+
+ /* Re-enable tx-fifo empty interrupt, if packets are stil pending */
+ if (len)
+ dwc_reg_modify(global_regs, DWC_GINTSTS, 0,\
+ DWC_INTMSK_RXFIFO_NOT_EMPT);
+ return 1;
+}
+
+/**
+ * This function is called when dedicated Tx FIFO Empty interrupt occurs.
+ * The active request is checked for the next packet to be loaded into
+ * apropriate Tx FIFO.
+ */
+static int write_empty_tx_fifo(struct dwc_pcd *pcd, u32 epnum)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ ulong regs;
+ u32 txstatus = 0;
+ struct pcd_ep *ep;
+ u32 len;
+ int dwords;
+ u32 diepint = 0;
+
+ ep = get_in_ep(pcd, epnum);
+ regs = core_if->dev_if->in_ep_regs[epnum];
+ txstatus = dwc_reg_read(regs, DWC_DTXFSTS);
+
+ /*
+ * While there is space in the queue, space in the FIFO and data to
+ * tranfer, write packets to the Tx FIFO
+ */
+ len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count;
+ dwords = count_dwords(ep, len);
+ while (DWC_DTXFSTS_TXFSSPC_AVAI_RD(txstatus) >= dwords
+ && ep->dwc_ep.xfer_count < ep->dwc_ep.xfer_len
+ && ep->dwc_ep.xfer_len != 0) {
+ dwc_otg_ep_write_packet(core_if, &ep->dwc_ep, 0);
+ len = ep->dwc_ep.xfer_len - ep->dwc_ep.xfer_count;
+ dwords = count_dwords(ep, len);
+ txstatus = dwc_reg_read(regs, DWC_DTXFSTS);
+ }
+ /* Clear emptyintr */
+ diepint = DWC_DIEPINT_TXFIFO_EMPTY_RW(diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, epnum), 0, diepint);
+ return 1;
+}
+
+/**
+ * This function is called when the Device is disconnected. It stops any active
+ * requests and informs the Gadget driver of the disconnect.
+ */
+void dwc_otg_pcd_stop(struct dwc_pcd *pcd)
+{
+ int i, num_in_eps, num_out_eps;
+ struct pcd_ep *ep;
+ ulong global_regs = GET_CORE_IF(pcd)->core_global_regs;
+
+ num_in_eps = GET_CORE_IF(pcd)->dev_if->num_in_eps;
+ num_out_eps = GET_CORE_IF(pcd)->dev_if->num_out_eps;
+
+ /* Don't disconnect drivers more than once */
+ if (pcd->ep0state == EP0_DISCONNECT)
+ return;
+ pcd->ep0state = EP0_DISCONNECT;
+
+ /* Reset the OTG state. */
+ dwc_otg_pcd_update_otg(pcd, 1);
+
+ /* Disable the NP Tx Fifo Empty Interrupt. */
+ dwc_reg_modify(global_regs, DWC_GINTMSK, DWC_INTMSK_NP_TXFIFO_EMPT, 0);
+
+ /* Flush the FIFOs */
+ dwc_otg_flush_tx_fifo(GET_CORE_IF(pcd), 0);
+ dwc_otg_flush_rx_fifo(GET_CORE_IF(pcd));
+
+ /* Prevent new request submissions, kill any outstanding requests */
+ ep = &pcd->ep0;
+ request_nuke(ep);
+
+ /* Prevent new request submissions, kill any outstanding requests */
+ for (i = 0; i < num_in_eps; i++)
+ request_nuke((struct pcd_ep *)&pcd->in_ep[i]);
+
+ /* Prevent new request submissions, kill any outstanding requests */
+ for (i = 0; i < num_out_eps; i++)
+ request_nuke((struct pcd_ep *)&pcd->out_ep[i]);
+
+ /* Report disconnect; the driver is already quiesced */
+ if (pcd->driver && pcd->driver->disconnect) {
+ spin_unlock(&pcd->lock);
+ pcd->driver->disconnect(&pcd->gadget);
+ spin_lock(&pcd->lock);
+ }
+}
+
+/**
+ * This interrupt indicates that ...
+ */
+static int dwc_otg_pcd_handle_i2c_intr(struct dwc_pcd *pcd)
+{
+ u32 gintsts;
+
+ pr_info("Interrupt handler not implemented for i2cintr\n");
+
+ /* Turn off and clean the interrupt */
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ DWC_INTMSK_I2C_INTR, 0);
+
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ DWC_INTSTS_I2C_INTR);
+ return 1;
+}
+
+/**
+ * This interrupt indicates that ...
+ */
+static int dwc_otg_pcd_handle_early_suspend_intr(struct dwc_pcd *pcd)
+{
+ u32 gintsts;
+
+ pr_info("Early Suspend Detected\n");
+
+ /* Turn off and clean the interrupt */
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ DWC_INTMSK_EARLY_SUSP, 0);
+
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ DWC_INTSTS_EARLY_SUSP);
+ return 1;
+}
+
+/**
+ * This function configures EPO to receive SETUP packets.
+ *
+ * Program the following fields in the endpoint specific registers for Control
+ * OUT EP 0, in order to receive a setup packet:
+ *
+ * - DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back setup packets)
+ *
+ * - DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back to back setup
+ * packets)
+ *
+ * In DMA mode, DOEPDMA0 Register with a memory address to store any setup
+ * packets received
+ */
+static void ep0_out_start(struct core_if *core_if, struct dwc_pcd *pcd)
+{
+ struct device_if *dev_if = core_if->dev_if;
+ u32 doeptsize0 = 0;
+
+ doeptsize0 = DWC_DEPTSIZ0_SUPCNT_RW(doeptsize0, 3);
+ doeptsize0 = DWC_DEPTSIZ0_PKT_CNT_RW(doeptsize0, 1);
+ doeptsize0 = DWC_DEPTSIZ0_XFER_SIZ_RW(doeptsize0, 8 * 3);
+ dwc_reg_write(dev_if->out_ep_regs[0], DWC_DOEPTSIZ, doeptsize0);
+
+ if (core_if->dma_enable) {
+ u32 doepctl = 0;
+
+ dwc_reg_write(dev_if->out_ep_regs[0], DWC_DOEPDMA,
+ pcd->setup_pkt_dma_handle);
+ doepctl = DWC_DEPCTL_EPENA_RW(doepctl, 1);
+ doepctl = DWC_DEPCTL_ACT_EP_RW(doepctl, 1);
+ dwc_reg_write(out_ep_ctl_reg(pcd, 0), 0, doepctl);
+ }
+}
+
+/**
+ * This interrupt occurs when a USB Reset is detected. When the USB Reset
+ * Interrupt occurs the device state is set to DEFAULT and the EP0 state is set
+ * to IDLE.
+ *
+ * Set the NAK bit for all OUT endpoints (DOEPCTLn.SNAK = 1)
+ *
+ * Unmask the following interrupt bits:
+ * - DAINTMSK.INEP0 = 1 (Control 0 IN endpoint)
+ * - DAINTMSK.OUTEP0 = 1 (Control 0 OUT endpoint)
+ * - DOEPMSK.SETUP = 1
+ * - DOEPMSK.XferCompl = 1
+ * - DIEPMSK.XferCompl = 1
+ * - DIEPMSK.TimeOut = 1
+ *
+ * Program the following fields in the endpoint specific registers for Control
+ * OUT EP 0, in order to receive a setup packet
+ * - DOEPTSIZ0.Packet Count = 3 (To receive up to 3 back to back setup packets)
+ * - DOEPTSIZE0.Transfer Size = 24 Bytes (To receive up to 3 back to back setup
+ * packets)
+ *
+ * - In DMA mode, DOEPDMA0 Register with a memory address to store any setup
+ * packets received
+ *
+ * At this point, all the required initialization, except for enabling
+ * the control 0 OUT endpoint is done, for receiving SETUP packets.
+ *
+ * Note that the bits in the Device IN endpoint mask register (diepmsk) are laid
+ * out exactly the same as the Device IN endpoint interrupt register (diepint.)
+ * Likewise for Device OUT endpoint mask / interrupt registers (doepmsk /
+ * doepint.)
+ */
+static int dwc_otg_pcd_handle_usb_reset_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ struct device_if *dev_if = core_if->dev_if;
+ u32 doepctl = 0;
+ u32 daintmsk = 0;
+ u32 doepmsk = 0;
+ u32 diepmsk = 0;
+ u32 dcfg = 0;
+ u32 resetctl = 0;
+ u32 dctl = 0;
+ u32 i;
+ u32 gintsts = 0;
+
+ pr_info("USB RESET\n");
+
+ /* reset the HNP settings */
+ dwc_otg_pcd_update_otg(pcd, 1);
+
+ /* Clear the Remote Wakeup Signalling */
+ dctl = DEC_DCTL_REMOTE_WAKEUP_SIG(dctl, 1);
+ dwc_reg_modify(dev_ctl_reg(pcd), 0, dctl, 0);
+
+ /* Set NAK for all OUT EPs */
+ doepctl = DWC_DEPCTL_SET_NAK_RW(doepctl, 1);
+ for (i = 0; i <= dev_if->num_out_eps; i++)
+ dwc_reg_write(out_ep_ctl_reg(pcd, i), 0, doepctl);
+
+ /* Flush the NP Tx FIFO */
+ dwc_otg_flush_tx_fifo(core_if, 0);
+
+ /* Flush the Learning Queue */
+ resetctl |= DWC_RSTCTL_TKN_QUE_FLUSH;
+ dwc_reg_write(core_if->core_global_regs, DWC_GRSTCTL, resetctl);
+
+ daintmsk |= DWC_DAINT_INEP00;
+ daintmsk |= DWC_DAINT_OUTEP00;
+ dwc_reg_write(dev_if->dev_global_regs, DWC_DAINTMSK, daintmsk);
+
+ doepmsk = DWC_DOEPMSK_SETUP_DONE_RW(doepmsk, 1);
+ doepmsk = DWC_DOEPMSK_AHB_ERROR_RW(doepmsk, 1);
+ doepmsk = DWC_DOEPMSK_EP_DISA_RW(doepmsk, 1);
+ doepmsk = DWC_DOEPMSK_TX_COMPL_RW(doepmsk, 1);
+ dwc_reg_write(dev_if->dev_global_regs, DWC_DOEPMSK, doepmsk);
+
+ diepmsk = DWC_DIEPMSK_TX_CMPL_RW(diepmsk, 1);
+ diepmsk = DWC_DIEPMSK_TOUT_COND_RW(diepmsk, 1);
+ diepmsk = DWC_DIEPMSK_EP_DISA_RW(diepmsk, 1);
+ diepmsk = DWC_DIEPMSK_AHB_ERROR_RW(diepmsk, 1);
+ diepmsk = DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(diepmsk, 1);
+ dwc_reg_write(dev_if->dev_global_regs, DWC_DIEPMSK, diepmsk);
+
+ /* Reset Device Address */
+ dcfg = dwc_reg_read(dev_if->dev_global_regs, DWC_DCFG);
+ dcfg = DWC_DCFG_DEV_ADDR_WR(dcfg, 0);
+ dwc_reg_write(dev_if->dev_global_regs, DWC_DCFG, dcfg);
+
+ /* setup EP0 to receive SETUP packets */
+ ep0_out_start(core_if, pcd);
+
+ /* Clear interrupt */
+ gintsts = 0;
+ gintsts |= DWC_INTSTS_USB_RST;
+ dwc_reg_write((core_if->core_global_regs), DWC_GINTSTS, gintsts);
+
+ return 1;
+}
+
+/**
+ * Get the device speed from the device status register and convert it
+ * to USB speed constant.
+ */
+static int get_device_speed(struct dwc_pcd *pcd)
+{
+ u32 dsts = 0;
+ enum usb_device_speed speed = USB_SPEED_UNKNOWN;
+
+ dsts = dwc_reg_read(dev_sts_reg(pcd), 0);
+
+ switch (DWC_DSTS_ENUM_SPEED_RD(dsts)) {
+ case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ:
+ speed = USB_SPEED_HIGH;
+ break;
+ case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ:
+ case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ:
+ speed = USB_SPEED_FULL;
+ break;
+ case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ:
+ speed = USB_SPEED_LOW;
+ break;
+ }
+ return speed;
+}
+
+/**
+ * This function enables EP0 OUT to receive SETUP packets and configures EP0
+ * IN for transmitting packets. It is normally called when the "Enumeration
+ * Done" interrupt occurs.
+ */
+static void dwc_otg_ep0_activate(struct core_if *core_if, struct dwc_ep *ep)
+{
+ struct device_if *dev_if = core_if->dev_if;
+ u32 dsts;
+ u32 diepctl = 0;
+ u32 doepctl = 0;
+ u32 dctl = 0;
+
+ /* Read the Device Status and Endpoint 0 Control registers */
+ dsts = dwc_reg_read(dev_if->dev_global_regs, DWC_DSTS);
+ diepctl = dwc_reg_read(dev_if->in_ep_regs[0], DWC_DIEPCTL);
+ doepctl = dwc_reg_read(dev_if->out_ep_regs[0], DWC_DOEPCTL);
+
+ /* Set the MPS of the IN EP based on the enumeration speed */
+ switch (DWC_DSTS_ENUM_SPEED_RD(dsts)) {
+ case DWC_DSTS_ENUMSPD_HS_PHY_30MHZ_OR_60MHZ:
+ case DWC_DSTS_ENUMSPD_FS_PHY_30MHZ_OR_60MHZ:
+ case DWC_DSTS_ENUMSPD_FS_PHY_48MHZ:
+ diepctl = DWC_DEPCTL_MPS_RW(diepctl, DWC_DEP0CTL_MPS_64);
+ break;
+ case DWC_DSTS_ENUMSPD_LS_PHY_6MHZ:
+ diepctl = DWC_DEPCTL_MPS_RW(diepctl, DWC_DEP0CTL_MPS_8);
+ break;
+ }
+ dwc_reg_write(dev_if->in_ep_regs[0], DWC_DIEPCTL, diepctl);
+
+ /* Enable OUT EP for receive */
+ doepctl = DWC_DEPCTL_EPENA_RW(doepctl, 1);
+ dwc_reg_write(dev_if->out_ep_regs[0], DWC_DOEPCTL, doepctl);
+
+ dctl = DWC_DCTL_CLR_CLBL_NP_IN_NAK(dctl, 1);
+ dwc_reg_modify(dev_if->dev_global_regs, DWC_DCTL, dctl, dctl);
+}
+
+/**
+ * Read the device status register and set the device speed in the
+ * data structure.
+ * Set up EP0 to receive SETUP packets by calling dwc_ep0_activate.
+ */
+static int dwc_otg_pcd_handle_enum_done_intr(struct dwc_pcd *pcd)
+{
+ struct pcd_ep *ep0 = &pcd->ep0;
+ u32 gintsts;
+ u32 gusbcfg;
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ ulong global_regs = core_if->core_global_regs;
+ u32 gsnpsid = global_regs + DWC_GSNPSID;
+ u8 utmi16b, utmi8b;
+
+ if (gsnpsid >= (u32) 0x4f54260a) {
+ utmi16b = 5;
+ utmi8b = 9;
+ } else {
+ utmi16b = 4;
+ utmi8b = 8;
+ }
+ dwc_otg_ep0_activate(GET_CORE_IF(pcd), &ep0->dwc_ep);
+
+ pcd->ep0state = EP0_IDLE;
+ ep0->stopped = 0;
+ pcd->gadget.speed = get_device_speed(pcd);
+
+ gusbcfg = dwc_reg_read(global_regs, DWC_GUSBCFG);
+
+ /* Set USB turnaround time based on device speed and PHY interface. */
+ if (pcd->gadget.speed == USB_SPEED_HIGH) {
+ switch (DWC_HWCFG2_HS_PHY_TYPE_RD(core_if->hwcfg2)) {
+ case DWC_HWCFG2_HS_PHY_TYPE_ULPI:
+ gusbcfg =
+ (gusbcfg & (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(9);
+ break;
+ case DWC_HWCFG2_HS_PHY_TYPE_UTMI:
+ if (DWC_HWCFG4_UTMI_PHY_DATA_WIDTH_RD(core_if->hwcfg4)
+ == 0)
+ gusbcfg =
+ (gusbcfg &
+ (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(utmi8b);
+ else if (DWC_HWCFG4_UTMI_PHY_DATA_WIDTH_RD
+ (core_if->hwcfg4) == 1)
+ gusbcfg =
+ (gusbcfg &
+ (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(utmi16b);
+ else if (core_if->core_params->phy_utmi_width == 8)
+ gusbcfg =
+ (gusbcfg &
+ (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(utmi8b);
+ else
+ gusbcfg =
+ (gusbcfg &
+ (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(utmi16b);
+ break;
+ case DWC_HWCFG2_HS_PHY_TYPE_UTMI_ULPI:
+ if (gusbcfg & DWC_USBCFG_ULPI_UTMI_SEL) {
+ gusbcfg =
+ (gusbcfg &
+ (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(9);
+ } else {
+ if (core_if->core_params->phy_utmi_width == 16)
+ gusbcfg =
+ (gusbcfg &
+ (~
+ ((u32) DWC_USBCFG_TRN_TIME(0xf))))
+ | DWC_USBCFG_TRN_TIME(utmi16b);
+ else
+ gusbcfg =
+ (gusbcfg &
+ (~
+ ((u32) DWC_USBCFG_TRN_TIME(0xf))))
+ | DWC_USBCFG_TRN_TIME(utmi8b);
+ }
+ break;
+ }
+ } else {
+ /* Full or low speed */
+ gusbcfg = (gusbcfg & (~((u32) DWC_USBCFG_TRN_TIME(0xf)))) |
+ DWC_USBCFG_TRN_TIME(9);
+ }
+ dwc_reg_write(global_regs, DWC_GUSBCFG, gusbcfg);
+
+ /* Clear interrupt */
+ gintsts = 0;
+ gintsts |= DWC_INTSTS_ENUM_DONE;
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ gintsts);
+
+ return 1;
+}
+
+/**
+ * This interrupt indicates that the ISO OUT Packet was dropped due to
+ * Rx FIFO full or Rx Status Queue Full. If this interrupt occurs
+ * read all the data from the Rx FIFO.
+ */
+static int dwc_otg_pcd_handle_isoc_out_packet_dropped_intr(struct dwc_pcd *pcd)
+{
+ u32 gintsts;
+
+ pr_info("Interrupt Handler not implemented for ISOC Out " "Dropped\n");
+
+ /* Turn off and clear the interrupt */
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ DWC_INTMSK_ISYNC_OUTPKT_DRP, 0);
+
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ DWC_INTSTS_ISYNC_OUTPKT_DRP);
+ return 1;
+}
+
+/**
+ * This interrupt indicates the end of the portion of the micro-frame
+ * for periodic transactions. If there is a periodic transaction for
+ * the next frame, load the packets into the EP periodic Tx FIFO.
+ */
+static int dwc_otg_pcd_handle_end_periodic_frame_intr(struct dwc_pcd *pcd)
+{
+ u32 gintsts;
+
+ pr_info("Interrupt handler not implemented for End of "
+ "Periodic Portion of Micro-Frame Interrupt");
+
+ /* Turn off and clear the interrupt */
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ DWC_INTMSK_END_OF_PFRM, 0);
+
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ DWC_INTSTS_END_OF_PFRM);
+ return 1;
+}
+
+/**
+ * This interrupt indicates that EP of the packet on the top of the
+ * non-periodic Tx FIFO does not match EP of the IN Token received.
+ *
+ * The "Device IN Token Queue" Registers are read to determine the
+ * order the IN Tokens have been received. The non-periodic Tx FIFO is flushed,
+ * so it can be reloaded in the order seen in the IN Token Queue.
+ */
+static int dwc_otg_pcd_handle_ep_mismatch_intr(struct core_if *core_if)
+{
+ pr_info("Interrupt handler not implemented for End Point "
+ "Mismatch\n");
+
+ /* Turn off and clear the interrupt */
+ dwc_reg_modify((core_if->core_global_regs), DWC_GINTMSK,
+ DWC_INTMSK_ENDP_MIS_MTCH, 0);
+
+ dwc_reg_write((core_if->core_global_regs), DWC_GINTSTS,\
+ DWC_INTSTS_ENDP_MIS_MTCH);
+ return 1;
+}
+
+/**
+ * This funcion stalls EP0.
+ */
+static void ep0_do_stall(struct dwc_pcd *pcd, const int val)
+{
+ struct pcd_ep *ep0 = &pcd->ep0;
+ struct usb_ctrlrequest *ctrl = &pcd->setup_pkt->req;
+
+ pr_warning("req %02x.%02x protocol STALL; err %d\n",
+ ctrl->bRequestType, ctrl->bRequest, val);
+
+ ep0->dwc_ep.is_in = 1;
+ dwc_otg_ep_set_stall(pcd->otg_dev->core_if, &ep0->dwc_ep);
+
+ pcd->ep0.stopped = 1;
+ pcd->ep0state = EP0_IDLE;
+ ep0_out_start(GET_CORE_IF(pcd), pcd);
+}
+
+/**
+ * This functions delegates the setup command to the gadget driver.
+ */
+static void do_gadget_setup(struct dwc_pcd *pcd, struct usb_ctrlrequest *ctrl)
+{
+ if (pcd->driver && pcd->driver->setup) {
+ int ret;
+
+ spin_unlock(&pcd->lock);
+ ret = pcd->driver->setup(&pcd->gadget, ctrl);
+ spin_lock(&pcd->lock);
+
+ if (ret < 0)
+ ep0_do_stall(pcd, ret);
+
+ /** This is a g_file_storage gadget driver specific
+ * workaround: a DELAYED_STATUS result from the fsg_setup
+ * routine will result in the gadget queueing a EP0 IN status
+ * phase for a two-stage control transfer.
+ *
+ * Exactly the same as a SET_CONFIGURATION/SET_INTERFACE except
+ * that this is a class specific request. Need a generic way to
+ * know when the gadget driver will queue the status phase.
+ *
+ * Can we assume when we call the gadget driver setup() function
+ * that it will always queue and require the following flag?
+ * Need to look into this.
+ */
+ if (ret == 256 + 999)
+ pcd->request_config = 1;
+ }
+}
+
+/**
+ * This function starts the Zero-Length Packet for the IN status phase
+ * of a 2 stage control transfer.
+ */
+static void do_setup_in_status_phase(struct dwc_pcd *pcd)
+{
+ struct pcd_ep *ep0 = &pcd->ep0;
+
+ if (pcd->ep0state == EP0_STALL)
+ return;
+
+ pcd->ep0state = EP0_STATUS;
+
+ ep0->dwc_ep.xfer_len = 0;
+ ep0->dwc_ep.xfer_count = 0;
+ ep0->dwc_ep.is_in = 1;
+ ep0->dwc_ep.dma_addr = pcd->setup_pkt_dma_handle;
+ dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep);
+
+ /* Prepare for more SETUP Packets */
+ ep0_out_start(GET_CORE_IF(pcd), pcd);
+}
+
+/**
+ * This function starts the Zero-Length Packet for the OUT status phase
+ * of a 2 stage control transfer.
+ */
+static void do_setup_out_status_phase(struct dwc_pcd *pcd)
+{
+ struct pcd_ep *ep0 = &pcd->ep0;
+
+ if (pcd->ep0state == EP0_STALL)
+ return;
+ pcd->ep0state = EP0_STATUS;
+
+ ep0->dwc_ep.xfer_len = 0;
+ ep0->dwc_ep.xfer_count = 0;
+ ep0->dwc_ep.is_in = 0;
+ ep0->dwc_ep.dma_addr = pcd->setup_pkt_dma_handle;
+ dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep);
+
+ /* Prepare for more SETUP Packets */
+ ep0_out_start(GET_CORE_IF(pcd), pcd);
+}
+
+/**
+ * Clear the EP halt (STALL) and if pending requests start the
+ * transfer.
+ */
+static void pcd_clear_halt(struct dwc_pcd *pcd, struct pcd_ep *ep)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+
+ if (!ep->dwc_ep.stall_clear_flag)
+ dwc_otg_ep_clear_stall(core_if, &ep->dwc_ep);
+
+ /* Reactive the EP */
+ dwc_otg_ep_activate(core_if, &ep->dwc_ep);
+
+ if (ep->stopped) {
+ ep->stopped = 0;
+ /* If there is a request in the EP queue start it */
+
+ /*
+ * dwc_start_next_request(), outside of interpt contxt at some
+ * time after the current time, after a clear-halt setup packet.
+ * Still need to implement ep mismatch in the future if a gadget
+ * ever uses more than one endpoint at once
+ */
+ if (core_if->dma_enable) {
+ ep->queue_sof = 1;
+ tasklet_schedule(pcd->start_xfer_tasklet);
+ } else {
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * To re-enable this endpoint it's important to
+ * set this next_ep number. Otherwise the endpoint
+ * will not get active again after stalling.
+ */
+ dwc_start_next_request(ep);
+ }
+ }
+
+ /* Start Control Status Phase */
+ do_setup_in_status_phase(pcd);
+}
+
+/**
+ * This function is called when the SET_FEATURE TEST_MODE Setup packet is sent
+ * from the host. The Device Control register is written with the Test Mode
+ * bits set to the specified Test Mode. This is done as a tasklet so that the
+ * "Status" phase of the control transfer completes before transmitting the TEST
+ * packets.
+ *
+ */
+static void do_test_mode(unsigned long data)
+{
+ u32 dctl = 0;
+ struct dwc_pcd *pcd = (struct dwc_pcd *)data;
+ int test_mode = pcd->test_mode;
+
+ dctl = dwc_reg_read(dev_ctl_reg(pcd), 0);
+ switch (test_mode) {
+ case 1: /* TEST_J */
+ dctl = DWC_DCTL_TST_CTL(dctl, 1);
+ break;
+ case 2: /* TEST_K */
+ dctl = DWC_DCTL_TST_CTL(dctl, 2);
+ break;
+ case 3: /* TEST_SE0_NAK */
+ dctl = DWC_DCTL_TST_CTL(dctl, 3);
+ break;
+ case 4: /* TEST_PACKET */
+ dctl = DWC_DCTL_TST_CTL(dctl, 4);
+ break;
+ case 5: /* TEST_FORCE_ENABLE */
+ dctl = DWC_DCTL_TST_CTL(dctl, 5);
+ break;
+ }
+ dwc_reg_write(dev_ctl_reg(pcd), 0, dctl);
+}
+
+/**
+ * This function process the SET_FEATURE Setup Commands.
+ */
+static void do_set_feature(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ ulong regs = core_if->core_global_regs;
+ struct usb_ctrlrequest ctrl = pcd->setup_pkt->req;
+ int otg_cap = core_if->core_params->otg_cap;
+ u32 gotgctl = 0;
+
+ switch (ctrl.bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ switch (__le16_to_cpu(ctrl.wValue)) {
+ case USB_DEVICE_REMOTE_WAKEUP:
+ pcd->remote_wakeup_enable = 1;
+ break;
+ case USB_DEVICE_TEST_MODE:
+ /*
+ * Setup the Test Mode tasklet to do the Test
+ * Packet generation after the SETUP Status
+ * phase has completed.
+ */
+
+ pcd->test_mode_tasklet.next = NULL;
+ pcd->test_mode_tasklet.state = 0;
+ atomic_set(&pcd->test_mode_tasklet.count, 0);
+
+ pcd->test_mode_tasklet.func = do_test_mode;
+ pcd->test_mode_tasklet.data = (unsigned long)pcd;
+ pcd->test_mode = __le16_to_cpu(ctrl.wIndex) >> 8;
+ tasklet_schedule(&pcd->test_mode_tasklet);
+
+ break;
+ case USB_DEVICE_B_HNP_ENABLE:
+ /* dev may initiate HNP */
+ if (otg_cap == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) {
+ pcd->b_hnp_enable = 1;
+ dwc_otg_pcd_update_otg(pcd, 0);
+ /*
+ * gotgctl.devhnpen cleared by a
+ * USB Reset?
+ */
+ gotgctl |= DWC_GCTL_DEV_HNP_ENA;
+ gotgctl |= DWC_GCTL_HNP_REQ;
+ dwc_reg_write(regs, DWC_GOTGCTL, gotgctl);
+ } else {
+ ep0_do_stall(pcd, -EOPNOTSUPP);
+ }
+ break;
+ case USB_DEVICE_A_HNP_SUPPORT:
+ /* RH port supports HNP */
+ if (otg_cap == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) {
+ pcd->a_hnp_support = 1;
+ dwc_otg_pcd_update_otg(pcd, 0);
+ } else {
+ ep0_do_stall(pcd, -EOPNOTSUPP);
+ }
+ break;
+ case USB_DEVICE_A_ALT_HNP_SUPPORT:
+ /* other RH port does */
+ if (otg_cap == DWC_OTG_CAP_PARAM_HNP_SRP_CAPABLE) {
+ pcd->a_alt_hnp_support = 1;
+ dwc_otg_pcd_update_otg(pcd, 0);
+ } else {
+ ep0_do_stall(pcd, -EOPNOTSUPP);
+ }
+ break;
+ }
+ do_setup_in_status_phase(pcd);
+ break;
+ case USB_RECIP_INTERFACE:
+ do_gadget_setup(pcd, &ctrl);
+ break;
+ case USB_RECIP_ENDPOINT:
+ if (__le16_to_cpu(ctrl.wValue) == USB_ENDPOINT_HALT) {
+ struct pcd_ep *ep;
+
+ ep = get_ep_by_addr(pcd, __le16_to_cpu(ctrl.wIndex));
+
+ if (ep == NULL) {
+ ep0_do_stall(pcd, -EOPNOTSUPP);
+ return;
+ }
+
+ ep->stopped = 1;
+ dwc_otg_ep_set_stall(core_if, &ep->dwc_ep);
+ }
+ do_setup_in_status_phase(pcd);
+ break;
+ }
+}
+
+/**
+ * This function process the CLEAR_FEATURE Setup Commands.
+ */
+static void do_clear_feature(struct dwc_pcd *pcd)
+{
+ struct usb_ctrlrequest ctrl = pcd->setup_pkt->req;
+ struct pcd_ep *ep;
+
+ switch (ctrl.bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ switch (__le16_to_cpu(ctrl.wValue)) {
+ case USB_DEVICE_REMOTE_WAKEUP:
+ pcd->remote_wakeup_enable = 0;
+ break;
+ case USB_DEVICE_TEST_MODE:
+ /* Add CLEAR_FEATURE for TEST modes. */
+ break;
+ }
+ do_setup_in_status_phase(pcd);
+ break;
+ case USB_RECIP_ENDPOINT:
+ ep = get_ep_by_addr(pcd, __le16_to_cpu(ctrl.wIndex));
+ if (ep == NULL) {
+ ep0_do_stall(pcd, -EOPNOTSUPP);
+ return;
+ }
+
+ pcd_clear_halt(pcd, ep);
+ break;
+ }
+}
+
+/**
+ * This function processes SETUP commands. In Linux, the USB Command processing
+ * is done in two places - the first being the PCD and the second in the Gadget
+ * Driver (for example, the File-Backed Storage Gadget Driver).
+ *
+ * GET_STATUS: Command is processed as defined in chapter 9 of the USB 2.0
+ * Specification chapter 9
+ *
+ * CLEAR_FEATURE: The Device and Endpoint requests are the ENDPOINT_HALT feature
+ * is procesed, all others the interface requests are ignored.
+ *
+ * SET_FEATURE: The Device and Endpoint requests are processed by the PCD.
+ * Interface requests are passed to the Gadget Driver.
+ *
+ * SET_ADDRESS: PCD, Program the DCFG reg, with device address received
+ *
+ * GET_DESCRIPTOR: Gadget Driver, Return the requested descriptor
+ *
+ * SET_DESCRIPTOR: Gadget Driver, Optional - not implemented by any of the
+ * existing Gadget Drivers.
+ *
+ * SET_CONFIGURATION: Gadget Driver, Disable all EPs and enable EPs for new
+ * configuration.
+ *
+ * GET_CONFIGURATION: Gadget Driver, Return the current configuration
+ *
+ * SET_INTERFACE: Gadget Driver, Disable all EPs and enable EPs for new
+ * configuration.
+ *
+ * GET_INTERFACE: Gadget Driver, Return the current interface.
+ *
+ * SYNC_FRAME: Display debug message.
+ *
+ * When the SETUP Phase Done interrupt occurs, the PCD SETUP commands are
+ * processed by pcd_setup. Calling the Function Driver's setup function from
+ * pcd_setup processes the gadget SETUP commands.
+ */
+static void pcd_setup(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ struct device_if *dev_if = core_if->dev_if;
+ struct usb_ctrlrequest ctrl = pcd->setup_pkt->req;
+ struct pcd_ep *ep;
+ struct pcd_ep *ep0 = &pcd->ep0;
+ u16 *status = pcd->status_buf;
+ u32 doeptsize0 = 0;
+
+ doeptsize0 = dwc_reg_read(dev_if->out_ep_regs[0], DWC_DOEPTSIZ);
+
+ /* handle > 1 setup packet , assert error for now */
+ if (core_if->dma_enable && (DWC_DEPTSIZ0_SUPCNT_RD(doeptsize0) < 2))
+ pr_err("\n\n CANNOT handle > 1 setup packet in "
+ "DMA mode\n\n");
+
+ /* Clean up the request queue */
+ request_nuke(ep0);
+ ep0->stopped = 0;
+
+ if (ctrl.bRequestType & USB_DIR_IN) {
+ ep0->dwc_ep.is_in = 1;
+ pcd->ep0state = EP0_IN_DATA_PHASE;
+ } else {
+ ep0->dwc_ep.is_in = 0;
+ pcd->ep0state = EP0_OUT_DATA_PHASE;
+ }
+
+ if ((ctrl.bRequestType & USB_TYPE_MASK) != USB_TYPE_STANDARD) {
+ /*
+ * Handle non-standard (class/vendor) requests in the gadget
+ * driver
+ */
+ do_gadget_setup(pcd, &ctrl);
+ return;
+ }
+
+ switch (ctrl.bRequest) {
+ case USB_REQ_GET_STATUS:
+ switch (ctrl.bRequestType & USB_RECIP_MASK) {
+ case USB_RECIP_DEVICE:
+ *status = 0x1; /* Self powered */
+ *status |= pcd->remote_wakeup_enable << 1;
+ break;
+ case USB_RECIP_INTERFACE:
+ *status = 0;
+ break;
+ case USB_RECIP_ENDPOINT:
+ ep = get_ep_by_addr(pcd, __le16_to_cpu(ctrl.wIndex));
+ if (ep == NULL || __le16_to_cpu(ctrl.wLength) > 2) {
+ ep0_do_stall(pcd, -EOPNOTSUPP);
+ return;
+ }
+ *status = ep->stopped;
+ break;
+ }
+
+ *status = __cpu_to_le16(*status);
+
+ pcd->ep0_pending = 1;
+ ep0->dwc_ep.start_xfer_buff = (u8 *) status;
+ ep0->dwc_ep.xfer_buff = (u8 *) status;
+ ep0->dwc_ep.dma_addr = pcd->status_buf_dma_handle;
+ ep0->dwc_ep.xfer_len = 2;
+ ep0->dwc_ep.xfer_count = 0;
+ ep0->dwc_ep.total_len = ep0->dwc_ep.xfer_len;
+ dwc_otg_ep0_start_transfer(GET_CORE_IF(pcd), &ep0->dwc_ep);
+ break;
+ case USB_REQ_CLEAR_FEATURE:
+ do_clear_feature(pcd);
+ break;
+ case USB_REQ_SET_FEATURE:
+ do_set_feature(pcd);
+ break;
+ case USB_REQ_SET_ADDRESS:
+ if (ctrl.bRequestType == USB_RECIP_DEVICE) {
+ u32 dcfg = 0;
+
+ dcfg = DWC_DCFG_DEV_ADDR_WR(dcfg,
+ __le16_to_cpu(ctrl.wValue));
+ dwc_reg_modify(dev_if->dev_global_regs, DWC_DCFG,
+ 0, dcfg);
+ do_setup_in_status_phase(pcd);
+ return;
+ }
+ break;
+ case USB_REQ_SET_INTERFACE:
+ case USB_REQ_SET_CONFIGURATION:
+ pcd->request_config = 1; /* Configuration changed */
+ do_gadget_setup(pcd, &ctrl);
+ break;
+ case USB_REQ_SYNCH_FRAME:
+ do_gadget_setup(pcd, &ctrl);
+ break;
+ default:
+ /* Call the Gadget Driver's setup functions */
+ do_gadget_setup(pcd, &ctrl);
+ break;
+ }
+}
+
+/**
+ * This function completes the ep0 control transfer.
+ */
+static int ep0_complete_request(struct pcd_ep *ep)
+{
+ struct core_if *core_if = GET_CORE_IF(ep->pcd);
+ struct device_if *dev_if = core_if->dev_if;
+ ulong in_regs = dev_if->in_ep_regs[ep->dwc_ep.num];
+ u32 deptsiz = 0;
+ struct pcd_request *req;
+ int is_last = 0;
+ struct dwc_pcd *pcd = ep->pcd;
+
+ if (pcd->ep0_pending && list_empty(&ep->queue)) {
+ if (ep->dwc_ep.is_in)
+ do_setup_out_status_phase(pcd);
+ else
+ do_setup_in_status_phase(pcd);
+
+ pcd->ep0_pending = 0;
+ pcd->ep0state = EP0_STATUS;
+ return 1;
+ }
+
+ if (list_empty(&ep->queue))
+ return 0;
+
+ req = list_entry(ep->queue.next, struct pcd_request, queue);
+
+ if (pcd->ep0state == EP0_STATUS) {
+ is_last = 1;
+ } else if (ep->dwc_ep.is_in) {
+ deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ);
+
+ if (DWC_DEPTSIZ0_XFER_SIZ_RD(deptsiz) == 0) {
+ req->req.actual = ep->dwc_ep.xfer_count;
+ do_setup_out_status_phase(pcd);
+ }
+ } else {
+ /* This is ep0-OUT */
+ req->req.actual = ep->dwc_ep.xfer_count;
+ do_setup_in_status_phase(pcd);
+ }
+
+ /* Complete the request */
+ if (is_last) {
+ request_done(ep, req, 0);
+ ep->dwc_ep.start_xfer_buff = NULL;
+ ep->dwc_ep.xfer_buff = NULL;
+ ep->dwc_ep.xfer_len = 0;
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * This function completes the request for the EP. If there are additional
+ * requests for the EP in the queue they will be started.
+ */
+static void complete_ep(struct pcd_ep *ep)
+{
+ struct core_if *core_if = GET_CORE_IF(ep->pcd);
+ struct device_if *dev_if = core_if->dev_if;
+ ulong in_ep_regs = dev_if->in_ep_regs[ep->dwc_ep.num];
+ u32 deptsiz = 0;
+ struct pcd_request *req = NULL;
+ int is_last = 0;
+
+ /* Get any pending requests */
+ if (!list_empty(&ep->queue))
+ req = list_entry(ep->queue.next, struct pcd_request, queue);
+
+ if (ep->dwc_ep.is_in) {
+ deptsiz = dwc_reg_read(in_ep_regs, DWC_DIEPTSIZ);
+
+ if (core_if->dma_enable && !DWC_DEPTSIZ_XFER_SIZ_RD(deptsiz))
+ ep->dwc_ep.xfer_count = ep->dwc_ep.xfer_len;
+
+ if (DWC_DEPTSIZ_XFER_SIZ_RD(deptsiz) == 0 &&
+ DWC_DEPTSIZ_PKT_CNT_RD(deptsiz) == 0 &&
+ ep->dwc_ep.xfer_count == ep->dwc_ep.xfer_len)
+ is_last = 1;
+ else
+ pr_warning("Incomplete transfer (%s-%s "
+ "[siz=%d pkt=%d])\n", ep->ep.name,
+ ep->dwc_ep.is_in ? "IN" : "OUT",
+ DWC_DEPTSIZ_XFER_SIZ_RD(deptsiz),
+ DWC_DEPTSIZ_PKT_CNT_RD(deptsiz));
+ } else {
+ ulong out_ep_regs = dev_if->out_ep_regs[ep->dwc_ep.num];
+
+ deptsiz = dwc_reg_read(out_ep_regs, DWC_DOEPTSIZ);
+ is_last = 1;
+ }
+
+ /* Complete the request */
+ if (is_last) {
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * Since the 405EZ (Ultra) only support 2047 bytes as
+ * max transfer size, we have to split up bigger transfers
+ * into multiple transfers of 1024 bytes sized messages.
+ * I happens often, that transfers of 4096 bytes are
+ * required (zero-gadget, file_storage-gadget).
+ */
+ if ((dwc_has_feature(core_if, DWC_LIMITED_XFER)) &&
+ ep->dwc_ep.bytes_pending) {
+ ulong in_regs =
+ core_if->dev_if->in_ep_regs[ep->dwc_ep.num];
+ u32 intr_mask = 0;
+
+ ep->dwc_ep.xfer_len = ep->dwc_ep.bytes_pending;
+ if (ep->dwc_ep.xfer_len > MAX_XFER_LEN) {
+ ep->dwc_ep.bytes_pending = ep->dwc_ep.xfer_len -
+ MAX_XFER_LEN;
+ ep->dwc_ep.xfer_len = MAX_XFER_LEN;
+ } else {
+ ep->dwc_ep.bytes_pending = 0;
+ }
+
+ /*
+ * Restart the current transfer with the next "chunk"
+ * of data.
+ */
+ ep->dwc_ep.xfer_count = 0;
+
+ deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ);
+ deptsiz =
+ DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz,
+ ep->dwc_ep.xfer_len);
+ deptsiz =
+ DWC_DEPTSIZ_PKT_CNT_RW(deptsiz,
+ ((ep->dwc_ep.xfer_len - 1 +
+ ep->dwc_ep.maxpacket) /
+ ep->dwc_ep.maxpacket));
+ dwc_reg_write(in_regs, DWC_DIEPTSIZ, deptsiz);
+
+ intr_mask |= DWC_INTSTS_NP_TXFIFO_EMPT;
+ dwc_reg_modify((core_if->core_global_regs),
+ DWC_GINTSTS, intr_mask, 0);
+ dwc_reg_modify((core_if->core_global_regs),
+ DWC_GINTMSK, intr_mask, intr_mask);
+
+ /*
+ * Just return here if message was not completely
+ * transferred.
+ */
+ return;
+ }
+ if (core_if->dma_enable)
+ req->req.actual = ep->dwc_ep.xfer_len -
+ DWC_DEPTSIZ_XFER_SIZ_RD(deptsiz);
+ else
+ req->req.actual = ep->dwc_ep.xfer_count;
+
+ request_done(ep, req, 0);
+ ep->dwc_ep.start_xfer_buff = NULL;
+ ep->dwc_ep.xfer_buff = NULL;
+ ep->dwc_ep.xfer_len = 0;
+
+ /* If there is a request in the queue start it. */
+ dwc_start_next_request(ep);
+ }
+}
+
+/**
+ * This function continues control IN transfers started by
+ * dwc_otg_ep0_start_transfer, when the transfer does not fit in a
+ * single packet. NOTE: The DIEPCTL0/DOEPCTL0 registers only have one
+ * bit for the packet count.
+ */
+static void dwc_otg_ep0_continue_transfer(struct core_if *c_if,
+ struct dwc_ep *ep)
+{
+ if (ep->is_in) {
+ u32 depctl = 0;
+ u32 deptsiz = 0;
+ struct device_if *d_if = c_if->dev_if;
+ ulong in_regs = d_if->in_ep_regs[0];
+ u32 tx_status = 0;
+ ulong glbl_regs = c_if->core_global_regs;
+
+ tx_status = dwc_reg_read(glbl_regs, DWC_GNPTXSTS);
+
+ depctl = dwc_reg_read(in_regs, DWC_DIEPCTL);
+ deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ);
+
+ /*
+ * Program the transfer size and packet count as follows:
+ * xfersize = N * maxpacket + short_packet
+ * pktcnt = N + (short_packet exist ? 1 : 0)
+ */
+ if (ep->total_len - ep->xfer_count > ep->maxpacket)
+ deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz,
+ ep->maxpacket);
+ else
+ deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz,
+ (ep->total_len -
+ ep->xfer_count));
+
+ deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, 1);
+ ep->xfer_len += DWC_DEPTSIZ0_XFER_SIZ_RD(deptsiz);
+ dwc_reg_write(in_regs, DWC_DIEPTSIZ, deptsiz);
+
+ /* Write the DMA register */
+ if (DWC_HWCFG2_ARCH_RD(c_if->hwcfg2) == DWC_INT_DMA_ARCH)
+ dwc_reg_write(in_regs, DWC_DIEPDMA, ep->dma_addr);
+
+ /* EP enable, IN data in FIFO */
+ depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1);
+ depctl = DWC_DEPCTL_EPENA_RW(depctl, 1);
+ dwc_reg_write(in_regs, DWC_DIEPCTL, depctl);
+
+ /*
+ * Enable the Non-Periodic Tx FIFO empty interrupt, the
+ * data will be written into the fifo by the ISR.
+ */
+ if (!c_if->dma_enable) {
+ /* First clear it from GINTSTS */
+ dwc_reg_write(glbl_regs, DWC_GINTSTS, DWC_INTMSK_NP_TXFIFO_EMPT);
+
+ /* To avoid spurious NPTxFEmp intr */
+ dwc_reg_modify(glbl_regs, DWC_GINTMSK,
+ DWC_INTMSK_NP_TXFIFO_EMPT,
+ DWC_INTMSK_NP_TXFIFO_EMPT);
+ }
+ }
+}
+
+/**
+ * This function handles EP0 Control transfers.
+ *
+ * The state of the control tranfers are tracked in ep0state
+ */
+static void handle_ep0(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ struct pcd_ep *ep0 = &pcd->ep0;
+
+ switch (pcd->ep0state) {
+ case EP0_DISCONNECT:
+ break;
+ case EP0_IDLE:
+ pcd->request_config = 0;
+ pcd_setup(pcd);
+ break;
+ case EP0_IN_DATA_PHASE:
+ if (core_if->dma_enable)
+ /*
+ * For EP0 we can only program 1 packet at a time so we
+ * need to do the calculations after each complete.
+ * Call write_packet to make the calculations, as in
+ * slave mode, and use those values to determine if we
+ * can complete.
+ */
+ dwc_otg_ep_write_packet(core_if, &ep0->dwc_ep, 1);
+ else
+ dwc_otg_ep_write_packet(core_if, &ep0->dwc_ep, 0);
+
+ if (ep0->dwc_ep.xfer_count < ep0->dwc_ep.total_len)
+ dwc_otg_ep0_continue_transfer(core_if, &ep0->dwc_ep);
+ else
+ ep0_complete_request(ep0);
+ break;
+ case EP0_OUT_DATA_PHASE:
+ ep0_complete_request(ep0);
+ break;
+ case EP0_STATUS:
+ ep0_complete_request(ep0);
+ pcd->ep0state = EP0_IDLE;
+ ep0->stopped = 1;
+ ep0->dwc_ep.is_in = 0; /* OUT for next SETUP */
+
+ /* Prepare for more SETUP Packets */
+ if (core_if->dma_enable) {
+ ep0_out_start(core_if, pcd);
+ } else {
+ int i;
+ u32 diepctl = 0;
+
+ diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, 0), 0);
+ if (pcd->ep0.queue_sof) {
+ pcd->ep0.queue_sof = 0;
+ dwc_start_next_request(&pcd->ep0);
+ }
+
+ diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, 0), 0);
+ if (pcd->ep0.queue_sof) {
+ pcd->ep0.queue_sof = 0;
+ dwc_start_next_request(&pcd->ep0);
+ }
+
+ for (i = 0; i < core_if->dev_if->num_in_eps; i++) {
+ diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, i),
+ 0);
+
+ if (pcd->in_ep[i].queue_sof) {
+ pcd->in_ep[i].queue_sof = 0;
+ dwc_start_next_request(&pcd->in_ep[i]);
+ }
+ }
+ }
+ break;
+ case EP0_STALL:
+ pr_err("EP0 STALLed, should not get here handle_ep0()\n");
+ break;
+ }
+}
+
+/**
+ * Restart transfer
+ */
+static void restart_transfer(struct dwc_pcd *pcd, const u32 ep_num)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ struct device_if *dev_if = core_if->dev_if;
+ u32 dieptsiz = 0;
+ struct pcd_ep *ep;
+
+ dieptsiz = dwc_reg_read(dev_if->in_ep_regs[ep_num], DWC_DIEPTSIZ);
+ ep = get_in_ep(pcd, ep_num);
+
+ /*
+ * If pktcnt is not 0, and xfersize is 0, and there is a buffer,
+ * resend the last packet.
+ */
+ if (DWC_DEPTSIZ_PKT_CNT_RD(dieptsiz) &&
+ !DWC_DEPTSIZ_XFER_SIZ_RD(dieptsiz) && ep->dwc_ep.start_xfer_buff) {
+ if (ep->dwc_ep.xfer_len <= ep->dwc_ep.maxpacket) {
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.xfer_buff = ep->dwc_ep.start_xfer_buff;
+ } else {
+ ep->dwc_ep.xfer_count -= ep->dwc_ep.maxpacket;
+
+ /* convert packet size to dwords. */
+ ep->dwc_ep.xfer_buff -= ep->dwc_ep.maxpacket;
+ }
+ ep->stopped = 0;
+
+ if (!ep_num)
+ dwc_otg_ep0_start_transfer(core_if, &ep->dwc_ep);
+ else
+ dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep);
+ }
+}
+
+/**
+ * Handle the IN EP Transfer Complete interrupt.
+ *
+ * If dedicated fifos are enabled, then the Tx FIFO empty interrupt for the EP
+ * is disabled. Otherwise the NP Tx FIFO empty interrupt is disabled.
+ */
+static void handle_in_ep_xfr_complete_intr(struct dwc_pcd *pcd,
+ struct pcd_ep *ep, u32 num)
+{
+ struct core_if *c_if = GET_CORE_IF(pcd);
+ struct device_if *d_if = c_if->dev_if;
+ struct dwc_ep *dwc_ep = &ep->dwc_ep;
+ u32 diepint = 0;
+
+ if (c_if->en_multiple_tx_fifo) {
+ u32 fifoemptymsk = 0x1 << dwc_ep->num;
+ dwc_reg_modify(d_if->dev_global_regs,
+ DWC_DTKNQR4FIFOEMPTYMSK, fifoemptymsk, 0);
+ } else {
+ u32 intr_mask = 0;
+
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify((c_if->core_global_regs), DWC_GINTMSK,
+ intr_mask, 0);
+ }
+
+ /* Clear the interrupt, then complete the transfer */
+ diepint = DWC_DIEPINT_TX_CMPL_RW(diepint, 1);
+ dwc_reg_write(d_if->in_ep_regs[num], DWC_DIEPINT, diepint);
+
+ if (!num)
+ handle_ep0(pcd);
+ else
+ complete_ep(ep);
+}
+
+/**
+ * Handle the IN EP disable interrupt.
+ */
+static void handle_in_ep_disable_intr(struct dwc_pcd *pcd, const u32 ep_num)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ struct device_if *dev_if = core_if->dev_if;
+ u32 dieptsiz = 0;
+ u32 dctl = 0;
+ struct pcd_ep *ep;
+ struct dwc_ep *dwc_ep;
+ u32 diepint = 0;
+
+ ep = get_in_ep(pcd, ep_num);
+ if (ep == NULL)
+ return;
+ dwc_ep = &ep->dwc_ep;
+
+ dieptsiz = dwc_reg_read(dev_if->in_ep_regs[ep_num], DWC_DIEPTSIZ);
+
+ if (ep->stopped) {
+ /* Flush the Tx FIFO */
+ dwc_otg_flush_tx_fifo(core_if, dwc_ep->tx_fifo_num);
+
+ /* Clear the Global IN NP NAK */
+ dctl = 0;
+ dctl = DWC_DCTL_CLR_CLBL_NP_IN_NAK(dctl, 1);
+ dwc_reg_modify(dev_ctl_reg(pcd), 0, dctl, 0);
+
+ if (DWC_DEPTSIZ_PKT_CNT_RD(dieptsiz) ||
+ DWC_DEPTSIZ_XFER_SIZ_RD(dieptsiz))
+ restart_transfer(pcd, ep_num);
+ } else {
+ if (DWC_DEPTSIZ_PKT_CNT_RD(dieptsiz) ||
+ DWC_DEPTSIZ_XFER_SIZ_RD(dieptsiz))
+ restart_transfer(pcd, ep_num);
+ }
+ /* Clear epdisabled */
+ diepint = DWC_DIEPINT_EP_DISA_RW(diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, ep_num), 0, diepint);
+
+}
+
+/**
+ * Handler for the IN EP timeout handshake interrupt.
+ */
+static void handle_in_ep_timeout_intr(struct dwc_pcd *pcd, const u32 ep_num)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ struct pcd_ep *ep;
+ u32 dctl = 0;
+ u32 diepint = 0;
+
+ ep = get_in_ep(pcd, ep_num);
+ if (ep == NULL)
+ return;
+
+ /* Disable the NP Tx Fifo Empty Interrrupt */
+ if (!core_if->dma_enable) {
+ dwc_reg_modify((core_if->core_global_regs), DWC_GINTMSK,
+ DWC_INTMSK_NP_TXFIFO_EMPT, 0);
+ }
+
+ /* Non-periodic EP */
+ /* Enable the Global IN NAK Effective Interrupt */
+ dwc_reg_modify((core_if->core_global_regs), DWC_GINTMSK, 0,
+ DWC_INTMSK_GLBL_IN_NAK);
+
+ /* Set Global IN NAK */
+ dctl = DWC_DCTL_CLR_CLBL_NP_IN_NAK(dctl, 1);
+ dwc_reg_modify(dev_ctl_reg(pcd), 0, dctl, dctl);
+ ep->stopped = 1;
+
+ /* Clear timeout */
+ diepint = DWC_DIEPINT_TOUT_COND_RW(diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, ep_num), 0, diepint);
+}
+
+/**
+ * Handles the IN Token received with TxF Empty interrupt.
+ *
+ * For the 405EZ, only start the next transfer, when currently no other transfer
+ * is active on this endpoint.
+ *
+ * Note that the bits in the Device IN endpoint mask register are laid out
+ * exactly the same as the Device IN endpoint interrupt register.
+ */
+static void handle_in_ep_tx_fifo_empty_intr(struct dwc_pcd *pcd,
+ struct pcd_ep *ep, u32 num)
+{
+ u32 diepint = 0;
+
+ if (!ep->stopped && num) {
+ u32 diepmsk = 0;
+
+ diepmsk = DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(diepmsk, 1);
+ dwc_reg_modify(dev_diepmsk_reg(pcd), 0, diepmsk, 0);
+
+ if (dwc_has_feature(GET_CORE_IF(pcd), DWC_LIMITED_XFER)) {
+ if (!ep->dwc_ep.active)
+ dwc_start_next_request(ep);
+ } else {
+ dwc_start_next_request(ep);
+ }
+ }
+ /* Clear intktxfemp */
+ diepint = DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, num), 0, diepint);
+}
+
+static void handle_in_ep_nak_effective_intr(struct dwc_pcd *pcd,
+ struct pcd_ep *ep, u32 num)
+{
+ u32 diepctl = 0;
+ u32 diepint = 0;
+
+ /* Periodic EP */
+ if (ep->disabling) {
+ diepctl = 0;
+ diepctl = DWC_DEPCTL_SET_NAK_RW(diepctl, 1);
+ diepctl = DWC_DEPCTL_DPID_RW(diepctl, 1);
+ dwc_reg_modify(in_ep_ctl_reg(pcd, num), 0, diepctl, diepctl);
+ }
+ /* Clear inepnakeff */
+ diepint = DWC_DIEPINT_IN_EP_NAK_RW(diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, num), 0, diepint);
+
+}
+
+/**
+ * This function returns the Device IN EP Interrupt register
+ */
+static inline u32 dwc_otg_read_diep_intr(struct core_if *core_if,
+ struct dwc_ep *ep)
+{
+ struct device_if *dev_if = core_if->dev_if;
+ u32 v, msk, emp;
+
+ msk = dwc_reg_read(dev_if->dev_global_regs, DWC_DIEPMSK);
+ emp =
+ dwc_reg_read(dev_if->dev_global_regs, DWC_DTKNQR4FIFOEMPTYMSK);
+ msk |= ((emp >> ep->num) & 0x1) << 7;
+ v = dwc_reg_read(dev_if->in_ep_regs[ep->num], DWC_DIEPINT) & msk;
+ return v;
+}
+
+/**
+ * This function reads the Device All Endpoints Interrupt register and
+ * returns the IN endpoint interrupt bits.
+ */
+static inline u32 dwc_otg_read_dev_all_in_ep_intr(struct core_if *_if)
+{
+ u32 v;
+
+ v = dwc_reg_read(_if->dev_if->dev_global_regs, DWC_DAINT) &
+ dwc_reg_read(_if->dev_if->dev_global_regs, DWC_DAINTMSK);
+ return v & 0xffff;
+}
+
+/**
+ * This interrupt indicates that an IN EP has a pending Interrupt.
+ * The sequence for handling the IN EP interrupt is shown below:
+ *
+ * - Read the Device All Endpoint Interrupt register
+ * - Repeat the following for each IN EP interrupt bit set (from LSB to MSB).
+ *
+ * - Read the Device Endpoint Interrupt (DIEPINTn) register
+ * - If "Transfer Complete" call the request complete function
+ * - If "Endpoint Disabled" complete the EP disable procedure.
+ * - If "AHB Error Interrupt" log error
+ * - If "Time-out Handshake" log error
+ * - If "IN Token Received when TxFIFO Empty" write packet to Tx FIFO.
+ * - If "IN Token EP Mismatch" (disable, this is handled by EP Mismatch
+ * Interrupt)
+ */
+static int dwc_otg_pcd_handle_in_ep_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ u32 diepint = 0;
+ u32 ep_intr;
+ u32 epnum = 0;
+ struct pcd_ep *ep;
+ struct dwc_ep *dwc_ep;
+
+ /* Read in the device interrupt bits */
+ ep_intr = dwc_otg_read_dev_all_in_ep_intr(core_if);
+
+ /* Service the Device IN interrupts for each endpoint */
+ while (ep_intr) {
+ if (ep_intr & 0x1) {
+ u32 c_diepint;
+
+ /* Get EP pointer */
+ ep = get_in_ep(pcd, epnum);
+ if (ep == NULL)
+ return;
+ dwc_ep = &ep->dwc_ep;
+
+ diepint = dwc_otg_read_diep_intr(core_if, dwc_ep);
+
+ /* Transfer complete */
+ if (DWC_DIEPINT_TX_CMPL_RD(diepint))
+ handle_in_ep_xfr_complete_intr(pcd, ep, epnum);
+
+ /* Endpoint disable */
+ if (DWC_DIEPINT_EP_DISA_RD(diepint))
+ handle_in_ep_disable_intr(pcd, epnum);
+
+ /* AHB Error */
+ if (DWC_DIEPINT_AHB_ERROR_RD(diepint)) {
+ /* Clear ahberr */
+ c_diepint = 0;
+ c_diepint =
+ DWC_DIEPINT_AHB_ERROR_RW(c_diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, epnum), 0,
+ c_diepint);
+ }
+
+ /* TimeOUT Handshake (non-ISOC IN EPs) */
+ if (DWC_DIEPINT_TOUT_COND_RD(diepint))
+ handle_in_ep_timeout_intr(pcd, epnum);
+
+ /* IN Token received with TxF Empty */
+ if (DWC_DIEPINT_IN_TKN_TX_EMPTY_RD(diepint))
+ handle_in_ep_tx_fifo_empty_intr(pcd, ep, epnum);
+
+ /* IN Token Received with EP mismatch */
+ if (DWC_DIEPINT_IN_TKN_EP_MISS_RD(diepint)) {
+ /* Clear intknepmis */
+ c_diepint = 0;
+ c_diepint =
+ DWC_DIEPINT_IN_TKN_EP_MISS_RW(c_diepint, 1);
+ dwc_reg_write(in_ep_int_reg(pcd, epnum), 0,
+ c_diepint);
+ }
+
+ /* IN Endpoint NAK Effective */
+ if (DWC_DIEPINT_IN_EP_NAK_RD(diepint))
+ handle_in_ep_nak_effective_intr(pcd, ep, epnum);
+
+ /* IN EP Tx FIFO Empty Intr */
+ if (DWC_DIEPINT_TXFIFO_EMPTY_RD(diepint))
+ write_empty_tx_fifo(pcd, epnum);
+ }
+ epnum++;
+ ep_intr >>= 1;
+ }
+ return 1;
+}
+
+/**
+ * This function reads the Device All Endpoints Interrupt register and
+ * returns the OUT endpoint interrupt bits.
+ */
+static inline u32 dwc_otg_read_dev_all_out_ep_intr(struct core_if *_if)
+{
+ u32 v;
+
+ v = dwc_reg_read(_if->dev_if->dev_global_regs, DWC_DAINT) &
+ dwc_reg_read(_if->dev_if->dev_global_regs, DWC_DAINTMSK);
+ return (v & 0xffff0000) >> 16;
+}
+
+/**
+ * This function returns the Device OUT EP Interrupt register
+ */
+static inline u32 dwc_otg_read_doep_intr(struct core_if *core_if,
+ struct dwc_ep *ep)
+{
+ struct device_if *dev_if = core_if->dev_if;
+ u32 v;
+
+ v = dwc_reg_read(dev_if->out_ep_regs[ep->num], DWC_DOEPINT) &
+ dwc_reg_read(dev_if->dev_global_regs, DWC_DOEPMSK);
+ return v;
+}
+
+/**
+ * This interrupt indicates that an OUT EP has a pending Interrupt.
+ * The sequence for handling the OUT EP interrupt is shown below:
+ *
+ * - Read the Device All Endpoint Interrupt register.
+ * - Repeat the following for each OUT EP interrupt bit set (from LSB to MSB).
+ *
+ * - Read the Device Endpoint Interrupt (DOEPINTn) register
+ * - If "Transfer Complete" call the request complete function
+ * - If "Endpoint Disabled" complete the EP disable procedure.
+ * - If "AHB Error Interrupt" log error
+ * - If "Setup Phase Done" process Setup Packet (See Standard USB Command
+ * Processing)
+ */
+static int dwc_otg_pcd_handle_out_ep_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ u32 ep_intr;
+ u32 doepint = 0;
+ u32 epnum = 0;
+ struct dwc_ep *dwc_ep;
+
+ /* Read in the device interrupt bits */
+ ep_intr = dwc_otg_read_dev_all_out_ep_intr(core_if);
+ while (ep_intr) {
+ if (ep_intr & 0x1) {
+ u32 c_doepint = 0;
+
+ dwc_ep = &((get_out_ep(pcd, epnum))->dwc_ep);
+ doepint = dwc_otg_read_doep_intr(core_if, dwc_ep);
+
+ /* Transfer complete */
+ if (DWC_DOEPINT_TX_COMPL_RD(doepint)) {
+ /* Clear xfercompl */
+ c_doepint = 0;
+ c_doepint =
+ DWC_DOEPMSK_TX_COMPL_RW(c_doepint, 1);
+ dwc_reg_write(out_ep_int_reg(pcd, epnum), 0,
+ c_doepint);
+ if (epnum == 0)
+ handle_ep0(pcd);
+ else
+ complete_ep(get_out_ep(pcd, epnum));
+ }
+
+ /* Endpoint disable */
+ if (DWC_DOEPINT_EP_DISA_RD(doepint)) {
+ /* Clear epdisabled */
+ c_doepint = 0;
+ c_doepint =
+ DWC_DOEPMSK_EP_DISA_RW(c_doepint, 1);
+ dwc_reg_write(out_ep_int_reg(pcd, epnum), 0,
+ c_doepint);
+ }
+
+ /* AHB Error */
+ if (DWC_DOEPINT_AHB_ERROR_RD(doepint)) {
+ c_doepint = 0;
+ c_doepint =
+ DWC_DOEPMSK_AHB_ERROR_RW(c_doepint, 1);
+ dwc_reg_write(out_ep_int_reg(pcd, epnum), 0,
+ c_doepint);
+ }
+
+ /* Setup Phase Done (control EPs) */
+ if (DWC_DOEPINT_SETUP_DONE_RD(doepint)) {
+ c_doepint = 0;
+ c_doepint =
+ DWC_DOEPMSK_SETUP_DONE_RW(c_doepint, 1);
+ dwc_reg_write(out_ep_int_reg(pcd, epnum), 0,
+ c_doepint);
+ handle_ep0(pcd);
+ }
+ }
+ epnum++;
+ ep_intr >>= 1;
+ }
+ return 1;
+}
+
+/**
+ * Incomplete ISO IN Transfer Interrupt. This interrupt indicates one of the
+ * following conditions occurred while transmitting an ISOC transaction.
+ *
+ * - Corrupted IN Token for ISOC EP.
+ * - Packet not complete in FIFO.
+ *
+ * The follow actions should be taken:
+ * - Determine the EP
+ * - Set incomplete flag in dwc_ep structure
+ * - Disable EP. When "Endpoint Disabled" interrupt is received Flush FIFO
+ */
+static int dwc_otg_pcd_handle_incomplete_isoc_in_intr(struct dwc_pcd *pcd)
+{
+ pr_info("Interrupt handler not implemented for IN ISOC "
+ "Incomplete\n");
+
+ /* Turn off and clear the interrupt */
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ DWC_INTMSK_INCMP_IN_ATX, 0);
+
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ DWC_INTSTS_INCMP_IN_ATX);
+ return 1;
+}
+
+/**
+ * Incomplete ISO OUT Transfer Interrupt. This interrupt indicates that the
+ * core has dropped an ISO OUT packet. The following conditions can be the
+ * cause:
+ *
+ * - FIFO Full, the entire packet would not fit in the FIFO.
+ * - CRC Error
+ * - Corrupted Token
+ *
+ * The follow actions should be taken:
+ * - Determine the EP
+ * - Set incomplete flag in dwc_ep structure
+ * - Read any data from the FIFO
+ * - Disable EP. When "Endpoint Disabled" interrupt is received re-enable EP.
+ */
+static int dwc_otg_pcd_handle_incomplete_isoc_out_intr(struct dwc_pcd *pcd)
+{
+ pr_info("Interrupt handler not implemented for OUT ISOC "
+ "Incomplete\n");
+
+ /* Turn off and clear the interrupt */
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ DWC_INTMSK_INCMP_OUT_PTX, 0);
+
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ DWC_INTSTS_INCMP_OUT_PTX);
+ return 1;
+}
+
+/**
+ * This function handles the Global IN NAK Effective interrupt.
+ */
+static int dwc_otg_pcd_handle_in_nak_effective(struct dwc_pcd *pcd)
+{
+ struct device_if *dev_if = GET_CORE_IF(pcd)->dev_if;
+ u32 diepctl = 0;
+ u32 diepctl_rd = 0;
+ u32 i;
+
+ /* Disable all active IN EPs */
+ diepctl = DWC_DEPCTL_DPID_RW(diepctl, 1);
+ diepctl = DWC_DEPCTL_SET_NAK_RW(diepctl, 1);
+ for (i = 0; i <= dev_if->num_in_eps; i++) {
+ diepctl_rd = dwc_reg_read(in_ep_ctl_reg(pcd, i), 0);
+ if (DWC_DEPCTL_EPENA_RD(diepctl_rd))
+ dwc_reg_write(in_ep_ctl_reg(pcd, i), 0, diepctl);
+ }
+
+ /* Disable the Global IN NAK Effective Interrupt */
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ DWC_INTMSK_GLBL_IN_NAK, 0);
+
+ /* Clear interrupt */
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ DWC_INTSTS_GLBL_IN_NAK);
+ return 1;
+}
+
+/**
+ * This function handles the Global OUT NAK Effective interrupt.
+ */
+static int dwc_otg_pcd_handle_out_nak_effective(struct dwc_pcd *pcd)
+{
+ pr_info("Interrupt handler not implemented for Global IN "
+ "NAK Effective\n");
+
+ /* Turn off and clear the interrupt */
+ dwc_reg_modify((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTMSK,
+ DWC_INTMSK_GLBL_OUT_NAK, 0);
+
+ /* Clear goutnakeff */
+ dwc_reg_write((GET_CORE_IF(pcd)->core_global_regs), DWC_GINTSTS,
+ DWC_INTSTS_GLBL_OUT_NAK);
+ return 1;
+}
+
+/**
+ * PCD interrupt handler.
+ *
+ * The PCD handles the device interrupts. Many conditions can cause a
+ * device interrupt. When an interrupt occurs, the device interrupt
+ * service routine determines the cause of the interrupt and
+ * dispatches handling to the appropriate function. These interrupt
+ * handling functions are described below.
+ *
+ * All interrupt registers are processed from LSB to MSB.
+ *
+ */
+int dwc_otg_pcd_handle_intr(struct dwc_pcd *pcd)
+{
+ struct core_if *core_if = GET_CORE_IF(pcd);
+ u32 gintr_status;
+ int ret = 0;
+
+ if (dwc_otg_is_device_mode(core_if)) {
+ spin_lock(&pcd->lock);
+
+ gintr_status = dwc_otg_read_core_intr(core_if);
+ if (!gintr_status) {
+ spin_unlock(&pcd->lock);
+ return 0;
+ }
+
+ if (gintr_status & DWC_INTSTS_STRT_OF_FRM)
+ ret |= dwc_otg_pcd_handle_sof_intr(pcd);
+ if (gintr_status & DWC_INTSTS_RXFIFO_NOT_EMPT)
+ ret |= dwc_otg_pcd_handle_rx_status_q_level_intr(pcd);
+ if (gintr_status & DWC_INTSTS_NP_TXFIFO_EMPT)
+ ret |= dwc_otg_pcd_handle_np_tx_fifo_empty_intr(pcd);
+ if (gintr_status & DWC_INTSTS_GLBL_IN_NAK)
+ ret |= dwc_otg_pcd_handle_in_nak_effective(pcd);
+ if (gintr_status & DWC_INTSTS_GLBL_OUT_NAK)
+ ret |= dwc_otg_pcd_handle_out_nak_effective(pcd);
+ if (gintr_status & DWC_INTSTS_I2C_INTR)
+ ret |= dwc_otg_pcd_handle_i2c_intr(pcd);
+ if (gintr_status & DWC_INTSTS_EARLY_SUSP)
+ ret |= dwc_otg_pcd_handle_early_suspend_intr(pcd);
+ if (gintr_status & DWC_INTSTS_USB_RST)
+ ret |= dwc_otg_pcd_handle_usb_reset_intr(pcd);
+ if (gintr_status & DWC_INTSTS_ENUM_DONE)
+ ret |= dwc_otg_pcd_handle_enum_done_intr(pcd);
+ if (gintr_status & DWC_INTSTS_ISYNC_OUTPKT_DRP)
+ ret |=
+ dwc_otg_pcd_handle_isoc_out_packet_dropped_intr
+ (pcd);
+ if (gintr_status & DWC_INTSTS_END_OF_PFRM)
+ ret |= dwc_otg_pcd_handle_end_periodic_frame_intr(pcd);
+ if (gintr_status & DWC_INTSTS_ENDP_MIS_MTCH)
+ ret |= dwc_otg_pcd_handle_ep_mismatch_intr(core_if);
+ if (gintr_status & DWC_INTSTS_IN_ENDP)
+ ret |= dwc_otg_pcd_handle_in_ep_intr(pcd);
+ if (gintr_status & DWC_INTSTS_OUT_ENDP)
+ ret |= dwc_otg_pcd_handle_out_ep_intr(pcd);
+ if (gintr_status & DWC_INTSTS_INCMP_IN_ATX)
+ ret |= dwc_otg_pcd_handle_incomplete_isoc_in_intr(pcd);
+ if (gintr_status & DWC_INTSTS_INCMP_OUT_PTX)
+ ret |= dwc_otg_pcd_handle_incomplete_isoc_out_intr(pcd);
+
+ spin_unlock(&pcd->lock);
+ }
+ return ret;
+}
--
1.7.0.4
^ permalink raw reply related
* [PATCH v16 07/10]USB/ppc4xx: Add Synopsys DWC OTG PCD function
From: Rupjyoti Sarmah @ 2012-05-03 12:36 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel; +Cc: rsarmah
The PCD is responsible for translating requests from the gadget driver
to appropriate actions on the DWC OTG controller.
Signed-off-by: Rupjyoti Sarmah <rsarmah@apm.com>
Signed-off-by: Tirumala R Marri <tmarri@apm.com>
Signed-off-by: Fushen Chen <fchen@apm.com>
Signed-off-by: Mark Miesfeld <mmiesfeld@apm.com>
---
drivers/usb/dwc/pcd.c | 1817 +++++++++++++++++++++++++++++++++++++++++++++++++
drivers/usb/dwc/pcd.h | 139 ++++
2 files changed, 1956 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/dwc/pcd.c
create mode 100644 drivers/usb/dwc/pcd.h
diff --git a/drivers/usb/dwc/pcd.c b/drivers/usb/dwc/pcd.c
new file mode 100644
index 0000000..f486d2d
--- /dev/null
+++ b/drivers/usb/dwc/pcd.c
@@ -0,0 +1,1817 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/*
+ * This file implements the Peripheral Controller Driver.
+ *
+ * The Peripheral Controller Driver (PCD) is responsible for
+ * translating requests from the Function Driver into the appropriate
+ * actions on the DWC_otg controller. It isolates the Function Driver
+ * from the specifics of the controller by providing an API to the
+ * Function Driver.
+ *
+ * The Peripheral Controller Driver for Linux will implement the
+ * Gadget API, so that the existing Gadget drivers can be used.
+ * (Gadget Driver is the Linux terminology for a Function Driver.)
+ *
+ * The Linux Gadget API is defined in the header file linux/usb/gadget.h. The
+ * USB EP operations API is defined in the structure usb_ep_ops and the USB
+ * Controller API is defined in the structure usb_gadget_ops
+ *
+ * An important function of the PCD is managing interrupts generated
+ * by the DWC_otg controller. The implementation of the DWC_otg device
+ * mode interrupt service routines is in dwc_otg_pcd_intr.c.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+
+#include "pcd.h"
+
+/*
+ * Static PCD pointer for use in usb_gadget_register_driver and
+ * usb_gadget_unregister_driver. Initialized in dwc_otg_pcd_init.
+ */
+static struct dwc_pcd *s_pcd;
+
+static inline int need_stop_srp_timer(struct core_if *core_if)
+{
+ if (core_if->core_params->phy_type != DWC_PHY_TYPE_PARAM_FS ||
+ !core_if->core_params->i2c_enable)
+ return core_if->srp_timer_started ? 1 : 0;
+ return 0;
+}
+
+/**
+ * Tests if the module is set to FS or if the PHY_TYPE is FS. If so, then the
+ * gadget should not report as dual-speed capable.
+ */
+static inline int check_is_dual_speed(struct core_if *core_if)
+{
+ if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL ||
+ (DWC_HWCFG2_HS_PHY_TYPE_RD(core_if->hwcfg2) == 2 &&
+ DWC_HWCFG2_P_2_P_RD(core_if->hwcfg2) == 1 &&
+ core_if->core_params->ulpi_fs_ls))
+ return 0;
+ return 1;
+}
+
+/**
+ * Tests if driver is OTG capable.
+ */
+static inline int check_is_otg(struct core_if *core_if)
+{
+ if (DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2) ==
+ DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE ||
+ DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2) ==
+ DWC_HWCFG2_OP_MODE_NO_SRP_CAPABLE_HOST ||
+ DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2) ==
+ DWC_HWCFG2_OP_MODE_SRP_CAPABLE_DEVICE ||
+ DWC_HWCFG2_OP_MODE_RD(core_if->hwcfg2) ==
+ DWC_HWCFG2_OP_MODE_SRP_CAPABLE_HOST)
+ return 0;
+ return 1;
+}
+
+/**
+ * This function completes a request. It calls the request call back.
+ */
+void request_done(struct pcd_ep *ep, struct pcd_request *req, int status)
+{
+ unsigned stopped = ep->stopped;
+
+ list_del_init(&req->queue);
+ if (req->req.status == -EINPROGRESS)
+ req->req.status = status;
+ else
+ status = req->req.status;
+
+ if (GET_CORE_IF(ep->pcd)->dma_enable) {
+ if (req->mapped) {
+ dma_unmap_single(ep->pcd->gadget.dev.parent,
+ req->req.dma, req->req.length,
+ ep->dwc_ep.is_in ? DMA_TO_DEVICE :
+ DMA_FROM_DEVICE);
+ req->req.dma = DMA_ADDR_INVALID;
+ req->mapped = 0;
+ } else {
+ dma_sync_single_for_cpu(ep->pcd->gadget.dev.parent,
+ req->req.dma, req->req.length,
+ ep->dwc_ep.
+ is_in ? DMA_TO_DEVICE :
+ DMA_FROM_DEVICE);
+ }
+ }
+
+ /* don't modify queue heads during completion callback */
+ ep->stopped = 1;
+ spin_unlock(&ep->pcd->lock);
+ req->req.complete(&ep->ep, &req->req);
+ spin_lock(&ep->pcd->lock);
+
+ if (ep->pcd->request_pending > 0)
+ --ep->pcd->request_pending;
+ ep->stopped = stopped;
+
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * Finally, when the current request is done, mark this endpoint
+ * as not active, so that new requests can be processed.
+ */
+ if (dwc_has_feature(GET_CORE_IF(ep->pcd), DWC_LIMITED_XFER))
+ ep->dwc_ep.active = 0;
+}
+
+/**
+ * This function terminates all the requsts in the EP request queue.
+ */
+void request_nuke(struct pcd_ep *ep)
+{
+ struct pcd_request *req;
+
+ ep->stopped = 1;
+
+ /* called with irqs blocked?? */
+ while (!list_empty(&ep->queue)) {
+ req = list_entry(ep->queue.next, struct pcd_request, queue);
+ request_done(ep, req, -ESHUTDOWN);
+ }
+}
+
+/*
+ * The following sections briefly describe the behavior of the Gadget
+ * API endpoint operations implemented in the DWC_otg driver
+ * software. Detailed descriptions of the generic behavior of each of
+ * these functions can be found in the Linux header file
+ * include/linux/usb_gadget.h.
+ *
+ * The Gadget API provides wrapper functions for each of the function
+ * pointers defined in usb_ep_ops. The Gadget Driver calls the wrapper
+ * function, which then calls the underlying PCD function. The
+ * following sections are named according to the wrapper
+ * functions. Within each section, the corresponding DWC_otg PCD
+ * function name is specified.
+ *
+ */
+
+/**
+ * This function assigns periodic Tx FIFO to an periodic EP in shared Tx FIFO
+ * mode
+ */
+static u32 assign_perio_tx_fifo(struct core_if *core_if)
+{
+ u32 mask = 1;
+ u32 i;
+
+ for (i = 0; i < DWC_HWCFG4_NUM_DEV_PERIO_IN_EP_RD(core_if->hwcfg4);
+ ++i) {
+ if (!(mask & core_if->p_tx_msk)) {
+ core_if->p_tx_msk |= mask;
+ return i + 1;
+ }
+ mask <<= 1;
+ }
+ return 0;
+}
+
+/**
+ * This function releases periodic Tx FIFO in shared Tx FIFO mode
+ */
+static void release_perio_tx_fifo(struct core_if *core_if, u32 fifo_num)
+{
+ core_if->p_tx_msk = (core_if->p_tx_msk & (1 << (fifo_num - 1)))
+ ^ core_if->p_tx_msk;
+}
+
+/**
+ * This function assigns periodic Tx FIFO to an periodic EP in shared Tx FIFO
+ * mode
+ */
+static u32 assign_tx_fifo(struct core_if *core_if)
+{
+ u32 mask = 1;
+ u32 i;
+
+ for (i = 0; i < DWC_HWCFG4_NUM_IN_EPS_RD(core_if->hwcfg4); ++i) {
+ if (!(mask & core_if->tx_msk)) {
+ core_if->tx_msk |= mask;
+ return i + 1;
+ }
+ mask <<= 1;
+ }
+ return 0;
+}
+
+/**
+ * This function releases periodic Tx FIFO in shared Tx FIFO mode
+ */
+static void release_tx_fifo(struct core_if *core_if, u32 fifo_num)
+{
+ core_if->tx_msk = (core_if->tx_msk & (1 << (fifo_num - 1)))
+ ^ core_if->tx_msk;
+}
+
+/**
+ * Sets an in endpoint's tx fifo based on the hardware configuration.
+ */
+static void set_in_ep_tx_fifo(struct dwc_pcd *pcd, struct pcd_ep *ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ if (pcd->otg_dev->core_if->en_multiple_tx_fifo) {
+ ep->dwc_ep.tx_fifo_num = assign_tx_fifo(pcd->otg_dev->core_if);
+ } else {
+ ep->dwc_ep.tx_fifo_num = 0;
+
+ /* If ISOC EP then assign a Periodic Tx FIFO. */
+ if ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
+ USB_ENDPOINT_XFER_ISOC)
+ ep->dwc_ep.tx_fifo_num =
+ assign_perio_tx_fifo(pcd->otg_dev->core_if);
+ }
+}
+
+/**
+ * This function activates an EP. The Device EP control register for
+ * the EP is configured as defined in the ep structure. Note: This function is
+ * not used for EP0.
+ */
+void dwc_otg_ep_activate(struct core_if *core_if, struct dwc_ep *ep)
+{
+ struct device_if *dev_if = core_if->dev_if;
+ u32 depctl = 0;
+ ulong addr;
+ u32 daintmsk = 0;
+
+ /* Read DEPCTLn register */
+ if (ep->is_in == 1) {
+ addr = dev_if->in_ep_regs[ep->num] + DWC_DIEPCTL;
+ daintmsk = DWC_DAINTMSK_IN_EP_RW(daintmsk, ep->num);
+ } else {
+ addr = dev_if->out_ep_regs[ep->num] + DWC_DOEPCTL;
+ daintmsk = DWC_DAINTMSK_OUT_EP_RW(daintmsk, ep->num);
+ }
+
+ /* If the EP is already active don't change the EP Control register */
+ depctl = dwc_reg_read(addr, 0);
+ if (!DWC_DEPCTL_ACT_EP_RD(depctl)) {
+ depctl = DWC_DEPCTL_MPS_RW(depctl, ep->maxpacket);
+ depctl = DWC_DEPCTL_EP_TYPE_RW(depctl, ep->type);
+ depctl = DWC_DEPCTL_TX_FIFO_NUM_RW(depctl, ep->tx_fifo_num);
+ depctl = DWC_DEPCTL_SET_DATA0_PID_RW(depctl, 1);
+ depctl = DWC_DEPCTL_ACT_EP_RW(depctl, 1);
+ dwc_reg_write(addr, 0, depctl);
+ }
+
+ /* Enable the Interrupt for this EP */
+ dwc_reg_modify(dev_if->dev_global_regs, DWC_DAINTMSK, 0, daintmsk);
+ ep->stall_clear_flag = 0;
+}
+
+/**
+ * This function is called by the Gadget Driver for each EP to be
+ * configured for the current configuration (SET_CONFIGURATION).
+ *
+ * This function initializes the dwc_otg_ep_t data structure, and then
+ * calls dwc_otg_ep_activate.
+ */
+static int dwc_otg_pcd_ep_enable(struct usb_ep *_ep,
+ const struct usb_endpoint_descriptor *desc)
+{
+ struct pcd_ep *ep;
+ struct dwc_pcd *pcd;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct pcd_ep, ep);
+ if (!_ep || !desc || ep->desc || desc->bDescriptorType !=
+ USB_DT_ENDPOINT) {
+ pr_warning("%s, bad ep or descriptor\n", __func__);
+ return -EINVAL;
+ }
+
+ if (ep == &ep->pcd->ep0) {
+ pr_warning("%s, bad ep(0)\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Check FIFO size */
+ if (!desc->wMaxPacketSize) {
+ pr_warning("%s, bad %s maxpacket\n", __func__, _ep->name);
+ return -ERANGE;
+ }
+
+ pcd = ep->pcd;
+ if (!pcd->driver || pcd->gadget.speed == USB_SPEED_UNKNOWN) {
+ pr_warning("%s, bogus device state\n", __func__);
+ return -ESHUTDOWN;
+ }
+
+ spin_lock_irqsave(&pcd->lock, flags);
+ ep->desc = desc;
+ ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize);
+
+ /* Activate the EP */
+ ep->stopped = 0;
+ ep->wedged = 0;
+ ep->dwc_ep.is_in = (USB_DIR_IN & desc->bEndpointAddress) != 0;
+ ep->dwc_ep.maxpacket = ep->ep.maxpacket;
+ ep->dwc_ep.type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+
+ if (ep->dwc_ep.is_in)
+ set_in_ep_tx_fifo(pcd, ep, desc);
+
+ /* Set initial data PID. */
+ if ((desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
+ USB_ENDPOINT_XFER_BULK)
+ ep->dwc_ep.data_pid_start = 0;
+
+ dwc_otg_ep_activate(GET_CORE_IF(pcd), &ep->dwc_ep);
+ spin_unlock_irqrestore(&pcd->lock, flags);
+ return 0;
+}
+
+/**
+ * This function deactivates an EP. This is done by clearing the USB Active EP
+ * bit in the Device EP control register. Note: This function is not used for
+ * EP0. EP0 cannot be deactivated.
+ */
+static void dwc_otg_ep_deactivate(struct core_if *core_if, struct dwc_ep *ep)
+{
+ u32 depctl = 0;
+ ulong addr;
+ u32 daintmsk = 0;
+
+ /* Read DEPCTLn register */
+ if (ep->is_in == 1) {
+ addr = core_if->dev_if->in_ep_regs[ep->num] + DWC_DIEPCTL;
+ daintmsk = DWC_DAINTMSK_IN_EP_RW(daintmsk, ep->num);
+ } else {
+ addr =
+ core_if->dev_if->out_ep_regs[ep->num] + DWC_DOEPCTL;
+ daintmsk = DWC_DAINTMSK_OUT_EP_RW(daintmsk, ep->num);
+ }
+
+ depctl = DWC_DEPCTL_ACT_EP_RW(depctl, 0);
+ dwc_reg_write(addr, 0, depctl);
+
+ /* Disable the Interrupt for this EP */
+ dwc_reg_modify(core_if->dev_if->dev_global_regs, DWC_DAINTMSK,
+ daintmsk, 0);
+}
+/**
+ *dwc_otg_ep_disable disables the EP as per specification. To be called
+ *during stall and disable self
+ */
+static void dwc_otg_ep_disable(struct core_if *core_if, struct dwc_ep *ep)
+{
+ u32 regs, diepctl, diepint;
+
+ regs = core_if->dev_if->in_ep_regs[ep->num];
+
+ /* Disable endpoint */
+ diepctl = dwc_reg_read(regs + DWC_DIEPCTL, 0);
+ diepctl = DWC_DEPCTL_SET_NAK_RW(diepctl, 1);
+ dwc_reg_write(regs + DWC_DIEPCTL, 0, diepctl);
+ diepint = dwc_reg_read(regs + DWC_DIEPINT, 0);
+ while (DWC_DIEPINT_IN_EP_NAK_RD(diepint) == 0)
+ diepint = dwc_reg_read(regs + DWC_DIEPINT, 0);
+ diepctl = DWC_DEPCTL_EPDIS_RW(diepctl, 1);
+ dwc_reg_write(regs + DWC_DIEPCTL, 0, diepctl);
+
+ /* Clear epdisabled , NAK effective*/
+ while (DWC_DIEPINT_EP_DISA_RD(diepint) == 0)
+ diepint = dwc_reg_read(regs + DWC_DIEPINT,0);
+ diepint = DWC_DIEPINT_EP_DISA_RW(diepint, 1);
+ diepint = DWC_DIEPINT_IN_EP_NAK_RW(diepint, 1);
+ dwc_reg_write(regs + DWC_DIEPINT, 0, diepint);
+}
+
+/**
+ * This function is called when an EP is disabled due to disconnect or
+ * change in configuration. Any pending requests will terminate with a
+ * status of -ESHUTDOWN.
+ *
+ * This function modifies the dwc_otg_ep_t data structure for this EP,
+ * and then calls dwc_otg_ep_deactivate.
+ */
+static int dwc_otg_pcd_ep_disable(struct usb_ep *_ep)
+{
+ struct pcd_ep *ep;
+ struct core_if *core_if;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct pcd_ep, ep);
+ if (!_ep || !ep->desc)
+ return -EINVAL;
+
+ core_if = ep->pcd->otg_dev->core_if;
+
+ spin_lock_irqsave(&ep->pcd->lock, flags);
+
+ request_nuke(ep);
+ dwc_otg_ep_deactivate(core_if, &ep->dwc_ep);
+
+ ep->desc = NULL;
+ ep->stopped = 1;
+ if (ep->dwc_ep.is_in) {
+ release_perio_tx_fifo(core_if, ep->dwc_ep.tx_fifo_num);
+ release_tx_fifo(core_if, ep->dwc_ep.tx_fifo_num);
+ }
+ dwc_otg_ep_deactivate(core_if, &ep->dwc_ep);
+ spin_unlock_irqrestore(&ep->pcd->lock, flags);
+
+ return 0;
+}
+
+/**
+ * This function allocates a request object to use with the specified
+ * endpoint.
+ */
+static struct usb_request *dwc_otg_pcd_alloc_request(struct usb_ep *_ep,
+ gfp_t gfp_flags)
+{
+ struct pcd_request *req;
+
+ if (!_ep) {
+ pr_warning("%s() Invalid EP\n", __func__);
+ return NULL;
+ }
+
+ req = kzalloc(sizeof(struct pcd_request), gfp_flags);
+ if (!req) {
+ pr_warning("%s() request allocation failed\n", __func__);
+ return NULL;
+ }
+
+ req->req.dma = DMA_ADDR_INVALID;
+ INIT_LIST_HEAD(&req->queue);
+
+ return &req->req;
+}
+
+/**
+ * This function frees a request object.
+ */
+static void dwc_otg_pcd_free_request(struct usb_ep *_ep,
+ struct usb_request *_req)
+{
+ struct pcd_request *req;
+
+ if (!_ep || !_req) {
+ pr_warning("%s() nvalid ep or req argument\n", __func__);
+ return;
+ }
+
+ req = container_of(_req, struct pcd_request, req);
+ kfree(req);
+}
+
+/*
+ * In dedicated Tx FIFO mode, enable the Non-Periodic Tx FIFO empty interrupt.
+ * Otherwise, enable the Tx FIFO epmty interrupt. The data will be written into
+ * the fifo by the ISR.
+ */
+static void enable_tx_fifo_empty_intr(struct core_if *c_if, struct dwc_ep *ep)
+{
+ u32 intr_mask = 0;
+ struct device_if *d_if = c_if->dev_if;
+ ulong global_regs = c_if->core_global_regs;
+
+ if (!c_if->en_multiple_tx_fifo) {
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(global_regs, DWC_GINTSTS, intr_mask, 0);
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, intr_mask);
+ } else if (ep->xfer_len) {
+ /* Enable the Tx FIFO Empty Interrupt for this EP */
+ u32 fifoemptymsk = 1 << ep->num;
+ dwc_reg_modify(d_if->dev_global_regs,
+ DWC_DTKNQR4FIFOEMPTYMSK, 0, fifoemptymsk);
+ }
+}
+
+static void set_next_ep(struct device_if *dev_if, u8 num)
+{
+ u32 depctl = 0;
+
+ depctl = dwc_reg_read(dev_if->in_ep_regs[0], 0) + DWC_DIEPCTL;
+ depctl = DWC_DEPCTL_NXT_EP_RW(depctl, num);
+
+ dwc_reg_write((dev_if->in_ep_regs[0]), DWC_DIEPCTL, depctl);
+}
+
+/**
+ * This function does the setup for a data transfer for an EP and
+ * starts the transfer. For an IN transfer, the packets will be loaded into the
+ * appropriate Tx FIFO in the ISR. For OUT transfers, the packets are unloaded
+ * from the Rx FIFO in the ISR.
+ *
+ */
+void dwc_otg_ep_start_transfer(struct core_if *c_if, struct dwc_ep *ep)
+{
+ u32 depctl = 0;
+ u32 deptsiz = 0;
+ struct device_if *d_if = c_if->dev_if;
+ ulong glbl_regs = c_if->core_global_regs;
+
+ if (ep->is_in) {
+ ulong in_regs = d_if->in_ep_regs[ep->num];
+ u32 gtxstatus;
+
+ gtxstatus = dwc_reg_read(glbl_regs, DWC_GNPTXSTS);
+ if (!c_if->en_multiple_tx_fifo
+ && !DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(gtxstatus))
+ return;
+
+ depctl = dwc_reg_read(in_regs, DWC_DIEPCTL);
+ deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ);
+
+ /* Zero Length Packet? */
+ if (!ep->xfer_len) {
+ deptsiz = DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz, 0);
+ deptsiz = DWC_DEPTSIZ_PKT_CNT_RW(deptsiz, 1);
+ } else {
+ /*
+ * Program the transfer size and packet count as
+ * follows:
+ *
+ * xfersize = N * maxpacket + short_packet
+ * pktcnt = N + (short_packet exist ? 1 : 0)
+ */
+
+ /*
+ * Added-sr: 2007-07-26
+ *
+ * Since the 405EZ (Ultra) only support 2047 bytes as
+ * max transfer size, we have to split up bigger
+ * transfers into multiple transfers of 1024 bytes sized
+ * messages. I happens often, that transfers of 4096
+ * bytes are required (zero-gadget,
+ * file_storage-gadget).
+ */
+ if (dwc_has_feature(c_if, DWC_LIMITED_XFER)) {
+ if (ep->xfer_len > MAX_XFER_LEN) {
+ ep->bytes_pending = ep->xfer_len
+ - MAX_XFER_LEN;
+ ep->xfer_len = MAX_XFER_LEN;
+ }
+ }
+
+ deptsiz =
+ DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz, ep->xfer_len);
+ deptsiz =
+ DWC_DEPTSIZ_PKT_CNT_RW(deptsiz,
+ ((ep->xfer_len - 1 +
+ ep->maxpacket) /
+ ep->maxpacket));
+ }
+ dwc_reg_write(in_regs, DWC_DIEPTSIZ, deptsiz);
+
+ if (c_if->dma_enable)
+ dwc_reg_write(in_regs, DWC_DIEPDMA, ep->dma_addr);
+ else if (ep->type != DWC_OTG_EP_TYPE_ISOC)
+ enable_tx_fifo_empty_intr(c_if, ep);
+
+ /* EP enable, IN data in FIFO */
+ depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1);
+ depctl = DWC_DEPCTL_EPENA_RW(depctl, 1);
+ dwc_reg_write(in_regs, DWC_DIEPCTL, depctl);
+
+ if (c_if->dma_enable)
+ set_next_ep(d_if, ep->num);
+ } else {
+ u32 out_regs = d_if->out_ep_regs[ep->num];
+
+ depctl = dwc_reg_read(out_regs, DWC_DOEPCTL);
+ deptsiz = dwc_reg_read(out_regs, DWC_DOEPTSIZ);
+
+ /*
+ * Program the transfer size and packet count as follows:
+ *
+ * pktcnt = N
+ * xfersize = N * maxpacket
+ */
+ if (!ep->xfer_len) {
+ deptsiz =
+ DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz, ep->maxpacket);
+ deptsiz = DWC_DEPTSIZ_PKT_CNT_RW(deptsiz, 1);
+ } else {
+ deptsiz = DWC_DEPTSIZ_PKT_CNT_RW(deptsiz,
+ ((ep->xfer_len +
+ ep->maxpacket -
+ 1) / ep->maxpacket));
+ deptsiz =
+ DWC_DEPTSIZ_XFER_SIZ_RW(deptsiz,
+ DWC_DEPTSIZ_PKT_CNT_RD
+ (deptsiz) * ep->maxpacket);
+ }
+ dwc_reg_write(out_regs, DWC_DOEPTSIZ, deptsiz);
+
+ if (c_if->dma_enable)
+ dwc_reg_write(out_regs, DWC_DOEPDMA, ep->dma_addr);
+
+ if (ep->type == DWC_OTG_EP_TYPE_ISOC) {
+ if (ep->even_odd_frame)
+ depctl = DWC_DEPCTL_SET_DATA1_PID_RW(depctl, 1);
+ else
+ depctl = DWC_DEPCTL_SET_DATA0_PID_RW(depctl, 1);
+ }
+
+ /* EP enable */
+ depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1);
+ depctl = DWC_DEPCTL_EPENA_RW(depctl, 1);
+ dwc_reg_write(out_regs, DWC_DOEPCTL, depctl);
+ }
+}
+
+/**
+ * This function does the setup for a data transfer for EP0 and starts
+ * the transfer. For an IN transfer, the packets will be loaded into
+ * the appropriate Tx FIFO in the ISR. For OUT transfers, the packets are
+ * unloaded from the Rx FIFO in the ISR.
+ */
+void dwc_otg_ep0_start_transfer(struct core_if *c_if, struct dwc_ep *ep)
+{
+ u32 depctl = 0;
+ u32 deptsiz = 0;
+ struct device_if *d_if = c_if->dev_if;
+ ulong glbl_regs = c_if->core_global_regs;
+
+ ep->total_len = ep->xfer_len;
+
+ if (ep->is_in) {
+ ulong in_regs = d_if->in_ep_regs[0];
+ u32 gtxstatus;
+
+ gtxstatus = dwc_reg_read(glbl_regs, DWC_GNPTXSTS);
+
+ if (!c_if->en_multiple_tx_fifo
+ && !DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(gtxstatus))
+ return;
+
+ depctl = dwc_reg_read(in_regs, DWC_DIEPCTL);
+ deptsiz = dwc_reg_read(in_regs, DWC_DIEPTSIZ);
+
+ /* Zero Length Packet? */
+ if (!ep->xfer_len) {
+ deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz, 0);
+ deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, 1);
+ } else {
+ /*
+ * Program the transfer size and packet count as
+ * follows:
+ *
+ * xfersize = N * maxpacket + short_packet
+ * pktcnt = N + (short_packet exist ? 1 : 0)
+ */
+ if (ep->xfer_len > ep->maxpacket) {
+ ep->xfer_len = ep->maxpacket;
+ deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz,
+ ep->
+ maxpacket);
+ } else {
+ deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz,
+ ep->
+ xfer_len);
+ }
+ deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, 1);
+ }
+ dwc_reg_write(in_regs, DWC_DIEPTSIZ, deptsiz);
+
+ if (c_if->dma_enable)
+ dwc_reg_write(in_regs, DWC_DIEPDMA, ep->dma_addr);
+
+ /* EP enable, IN data in FIFO */
+ depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1);
+ depctl = DWC_DEPCTL_EPENA_RW(depctl, 1);
+ dwc_reg_write(in_regs, DWC_DIEPCTL, depctl);
+
+ if (!c_if->dma_enable)
+ enable_tx_fifo_empty_intr(c_if, ep);
+ } else {
+ ulong out_regs = d_if->out_ep_regs[ep->num];
+
+ depctl = dwc_reg_read(out_regs, DWC_DOEPCTL);
+ deptsiz = dwc_reg_read(out_regs, DWC_DOEPTSIZ);
+
+ /*
+ * Program the transfer size and packet count as follows:
+ *
+ * xfersize = N * (maxpacket + 4 - (maxpacket % 4))
+ * pktcnt = N
+ */
+ if (!ep->xfer_len) {
+ deptsiz = DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz,
+ ep->maxpacket);
+ deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz, 1);
+ } else {
+ deptsiz = DWC_DEPTSIZ0_PKT_CNT_RW(deptsiz,
+ (ep->xfer_len +
+ ep->maxpacket -
+ 1) / ep->maxpacket);
+ deptsiz =
+ DWC_DEPTSIZ0_XFER_SIZ_RW(deptsiz,
+ DWC_DEPTSIZ_PKT_CNT_RD
+ (deptsiz) * ep->maxpacket);
+ }
+ dwc_reg_write(out_regs, DWC_DOEPTSIZ, deptsiz);
+
+ if (c_if->dma_enable)
+ dwc_reg_write(out_regs, DWC_DOEPDMA, ep->dma_addr);
+
+ /* EP enable */
+ depctl = DWC_DEPCTL_CLR_NAK_RW(depctl, 1);
+ depctl = DWC_DEPCTL_EPENA_RW(depctl, 1);
+ dwc_reg_write(out_regs, DWC_DOEPCTL, depctl);
+ }
+}
+
+/**
+ * This function is used to submit an I/O Request to an EP.
+ *
+ * - When the request completes the request's completion callback
+ * is called to return the request to the driver.
+ * - An EP, except control EPs, may have multiple requests
+ * pending.
+ * - Once submitted the request cannot be examined or modified.
+ * - Each request is turned into one or more packets.
+ * - A BULK EP can queue any amount of data; the transfer is
+ * packetized.
+ * - Zero length Packets are specified with the request 'zero'
+ * flag.
+ */
+static int dwc_otg_pcd_ep_queue(struct usb_ep *_ep, struct usb_request *_req,
+ gfp_t gfp_flags)
+{
+ struct pcd_request *req;
+ struct pcd_ep *ep;
+ struct dwc_pcd *pcd;
+ struct core_if *core_if;
+ unsigned long flags;
+
+ req = container_of(_req, struct pcd_request, req);
+ if (!_req || !_req->complete || !_req->buf ||
+ !list_empty(&req->queue)) {
+ pr_warning("%s, bad params\n", __func__);
+ return -EINVAL;
+ }
+
+ ep = container_of(_ep, struct pcd_ep, ep);
+ if (!_ep || (!ep->desc && ep->dwc_ep.num != 0)) {
+ pr_warning("%s, bad ep\n", __func__);
+ return -EINVAL;
+ }
+
+ pcd = ep->pcd;
+ if (!pcd->driver || pcd->gadget.speed == USB_SPEED_UNKNOWN) {
+ pr_warning("%s, bogus device state\n", __func__);
+ return -ESHUTDOWN;
+ }
+ core_if = pcd->otg_dev->core_if;
+
+ if (GET_CORE_IF(pcd)->dma_enable) {
+ if (_req->dma == DMA_ADDR_INVALID) {
+ _req->dma = dma_map_single(pcd->gadget.dev.parent,
+ _req->buf, _req->length,
+ ep->dwc_ep.
+ is_in ? DMA_TO_DEVICE :
+ DMA_FROM_DEVICE);
+ req->mapped = 1;
+ } else {
+ dma_sync_single_for_device(pcd->gadget.dev.parent,
+ _req->dma, _req->length,
+ ep->dwc_ep.
+ is_in ? DMA_TO_DEVICE :
+ DMA_FROM_DEVICE);
+ req->mapped = 0;
+ }
+ }
+
+ spin_lock_irqsave(&ep->pcd->lock, flags);
+
+ _req->status = -EINPROGRESS;
+ _req->actual = 0;
+
+ /* Start the transfer */
+ if (list_empty(&ep->queue) && !ep->stopped) {
+ /* EP0 Transfer? */
+ if (ep->dwc_ep.num == 0) {
+ switch (pcd->ep0state) {
+ case EP0_IN_DATA_PHASE:
+ break;
+ case EP0_OUT_DATA_PHASE:
+ if (pcd->request_config) {
+ /* Complete STATUS PHASE */
+ ep->dwc_ep.is_in = 1;
+ pcd->ep0state = EP0_STATUS;
+ }
+ break;
+ default:
+ spin_unlock_irqrestore(&pcd->lock, flags);
+ return -EL2HLT;
+ }
+
+ ep->dwc_ep.dma_addr = _req->dma;
+ ep->dwc_ep.start_xfer_buff = _req->buf;
+ ep->dwc_ep.xfer_buff = _req->buf;
+ ep->dwc_ep.xfer_len = _req->length;
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.sent_zlp = 0;
+ ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;
+
+ dwc_otg_ep0_start_transfer(core_if, &ep->dwc_ep);
+ } else {
+ /* Setup and start the Transfer */
+ ep->dwc_ep.dma_addr = _req->dma;
+ ep->dwc_ep.start_xfer_buff = _req->buf;
+ ep->dwc_ep.xfer_buff = _req->buf;
+ ep->dwc_ep.xfer_len = _req->length;
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.sent_zlp = 0;
+ ep->dwc_ep.total_len = ep->dwc_ep.xfer_len;
+
+ dwc_otg_ep_start_transfer(core_if, &ep->dwc_ep);
+ }
+ }
+
+ if (req) {
+ ++pcd->request_pending;
+ list_add_tail(&req->queue, &ep->queue);
+
+ if (ep->dwc_ep.is_in && ep->stopped && !core_if->dma_enable) {
+ /*
+ * Device IN endpoint interrupt mask register is laid
+ * out exactly the same as the device IN endpoint
+ * interrupt register.
+ */
+ u32 diepmsk = 0;
+ diepmsk = DWC_DIEPMSK_IN_TKN_TX_EMPTY_RW(diepmsk, 1);
+
+ dwc_reg_modify(core_if->dev_if->dev_global_regs,
+ DWC_DIEPMSK, 0, diepmsk);
+ }
+ }
+
+ spin_unlock_irqrestore(&pcd->lock, flags);
+ return 0;
+}
+
+/**
+ * This function cancels an I/O request from an EP.
+ */
+static int dwc_otg_pcd_ep_dequeue(struct usb_ep *_ep, struct usb_request *_req)
+{
+ struct pcd_request *req;
+ struct pcd_ep *ep;
+ struct dwc_pcd *pcd;
+ unsigned long flags;
+
+ ep = container_of(_ep, struct pcd_ep, ep);
+ if (!_ep || !_req || (!ep->desc && ep->dwc_ep.num != 0)) {
+ pr_warning("%s, bad argument\n", __func__);
+ return -EINVAL;
+ }
+
+ pcd = ep->pcd;
+ if (!pcd->driver || pcd->gadget.speed == USB_SPEED_UNKNOWN) {
+ pr_warning("%s, bogus device state\n", __func__);
+ return -ESHUTDOWN;
+ }
+
+ spin_lock_irqsave(&pcd->lock, flags);
+
+ /* make sure it's actually queued on this endpoint */
+ list_for_each_entry(req, &ep->queue, queue)
+ if (&req->req == _req)
+ break;
+
+ if (&req->req != _req) {
+ spin_unlock_irqrestore(&pcd->lock, flags);
+ return -EINVAL;
+ }
+
+ if (!list_empty(&req->queue))
+ request_done(ep, req, -ECONNRESET);
+ else
+ req = NULL;
+
+ spin_unlock_irqrestore(&pcd->lock, flags);
+
+ return req ? 0 : -EOPNOTSUPP;
+}
+
+/**
+ * Set the EP STALL.
+ */
+void dwc_otg_ep_set_stall(struct core_if *core_if, struct dwc_ep *ep)
+{
+ u32 depctl = 0;
+ ulong depctl_addr;
+
+ if (ep->is_in) {
+ depctl_addr =
+ (core_if->dev_if->in_ep_regs[ep->num]) + DWC_DIEPCTL;
+ depctl = dwc_reg_read(depctl_addr, 0);
+
+ /* set the disable and stall bits */
+ if (DWC_DEPCTL_EPENA_RD(depctl))
+ depctl = DWC_DEPCTL_EPDIS_RW(depctl, 1);
+ depctl = DWC_DEPCTL_STALL_HNDSHK_RW(depctl, 1);
+ dwc_reg_write(depctl_addr, 0, depctl);
+ dwc_otg_ep_disable(core_if, ep);
+ } else {
+ depctl_addr =
+ (core_if->dev_if->out_ep_regs[ep->num] + DWC_DOEPCTL);
+ depctl = dwc_reg_read(depctl_addr, 0);
+
+ /* set the stall bit */
+ depctl = DWC_DEPCTL_STALL_HNDSHK_RW(depctl, 1);
+ dwc_reg_write(depctl_addr, 0, depctl);
+ }
+}
+
+/**
+ * Clear the EP STALL.
+ */
+void dwc_otg_ep_clear_stall(struct core_if *core_if, struct dwc_ep *ep)
+{
+ u32 depctl = 0;
+ ulong depctl_addr;
+
+ if (ep->is_in == 1)
+ depctl_addr =
+ (core_if->dev_if->in_ep_regs[ep->num]) + DWC_DIEPCTL;
+ else
+ depctl_addr =
+ (core_if->dev_if->out_ep_regs[ep->num]) + DWC_DOEPCTL;
+
+ depctl = dwc_reg_read(depctl_addr, 0);
+
+ /* clear the stall bits */
+ depctl = DWC_DEPCTL_STALL_HNDSHK_RW(depctl, 0);
+
+ /*
+ * USB Spec 9.4.5: For endpoints using data toggle, regardless
+ * of whether an endpoint has the Halt feature set, a
+ * ClearFeature(ENDPOINT_HALT) request always results in the
+ * data toggle being reinitialized to DATA0.
+ */
+ if (ep->type == DWC_OTG_EP_TYPE_INTR ||
+ ep->type == DWC_OTG_EP_TYPE_BULK)
+ depctl = DWC_DEPCTL_SET_DATA0_PID_RW(depctl, 1);
+
+ dwc_reg_write(depctl_addr, 0, depctl);
+}
+
+/**
+ * usb_ep_set_halt stalls an endpoint.
+ *
+ * usb_ep_clear_halt clears an endpoint halt and resets its data
+ * toggle.
+ *
+ * Both of these functions are implemented with the same underlying
+ * function. The behavior depends on the val argument:
+ * - 0 means clear_halt.
+ * - 1 means set_halt,
+ * - 2 means clear stall lock flag.
+ * - 3 means set stall lock flag.
+ */
+static int dwc_otg_pcd_ep_set_halt_wedge(struct usb_ep *_ep,
+ int val, int wedged)
+{
+ int retval = 0;
+ unsigned long flags;
+ struct pcd_ep *ep;
+
+ ep = container_of(_ep, struct pcd_ep, ep);
+ if (!_ep || (!ep->desc && ep != &ep->pcd->ep0) ||
+ ep->desc->bmAttributes == USB_ENDPOINT_XFER_ISOC) {
+ pr_warning("%s, bad ep\n", __func__);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&ep->pcd->lock, flags);
+
+ if (ep->dwc_ep.is_in && !list_empty(&ep->queue)) {
+ pr_warning("%s() %s XFer In process\n", __func__, _ep->name);
+ retval = -EAGAIN;
+ } else if (val == 0) {
+ ep->wedged = 0;
+ dwc_otg_ep_clear_stall(ep->pcd->otg_dev->core_if, &ep->dwc_ep);
+ } else if (val == 1) {
+ if (ep->dwc_ep.num == 0)
+ ep->pcd->ep0state = EP0_STALL;
+ if (wedged)
+ ep->wedged = 1;
+
+ ep->stopped = 1;
+ dwc_otg_ep_set_stall(ep->pcd->otg_dev->core_if, &ep->dwc_ep);
+ } else if (val == 2) {
+ ep->dwc_ep.stall_clear_flag = 0;
+ } else if (val == 3) {
+ ep->dwc_ep.stall_clear_flag = 1;
+ }
+
+ spin_unlock_irqrestore(&ep->pcd->lock, flags);
+ return retval;
+}
+static int dwc_otg_pcd_ep_set_halt(struct usb_ep *_ep, int val)
+{
+ return dwc_otg_pcd_ep_set_halt_wedge(_ep, val, 0);
+}
+static int dwc_otg_pcd_ep_set_wedge(struct usb_ep *_ep)
+{
+ return dwc_otg_pcd_ep_set_halt_wedge(_ep, 1, 1);
+}
+
+static struct usb_ep_ops dwc_otg_pcd_ep_ops = {
+ .enable = dwc_otg_pcd_ep_enable,
+ .disable = dwc_otg_pcd_ep_disable,
+ .alloc_request = dwc_otg_pcd_alloc_request,
+ .free_request = dwc_otg_pcd_free_request,
+ .queue = dwc_otg_pcd_ep_queue,
+ .dequeue = dwc_otg_pcd_ep_dequeue,
+ .set_halt = dwc_otg_pcd_ep_set_halt,
+ .set_wedge = dwc_otg_pcd_ep_set_wedge,
+ .fifo_status = NULL,
+ .fifo_flush = NULL,
+};
+
+/**
+ * Gets the current USB frame number from the DTS register. This is the frame
+ * number from the last SOF packet.
+ */
+static u32 dwc_otg_get_frame_number(struct core_if *core_if)
+{
+ u32 dsts;
+
+ dsts = dwc_reg_read(core_if->dev_if->dev_global_regs, DWC_DSTS);
+ return DWC_DSTS_SOFFN_RD(dsts);
+}
+
+/**
+ * The following gadget operations will be implemented in the DWC_otg
+ * PCD. Functions in the API that are not described below are not
+ * implemented.
+ *
+ * The Gadget API provides wrapper functions for each of the function
+ * pointers defined in usb_gadget_ops. The Gadget Driver calls the
+ * wrapper function, which then calls the underlying PCD function. The
+ * following sections are named according to the wrapper functions
+ * (except for ioctl, which doesn't have a wrapper function). Within
+ * each section, the corresponding DWC_otg PCD function name is
+ * specified.
+ *
+ */
+
+/**
+ *Gets the USB Frame number of the last SOF.
+ */
+static int dwc_otg_pcd_get_frame(struct usb_gadget *_gadget)
+{
+ if (!_gadget) {
+ return -ENODEV;
+ } else {
+ struct dwc_pcd *pcd;
+
+ pcd = container_of(_gadget, struct dwc_pcd, gadget);
+ dwc_otg_get_frame_number(GET_CORE_IF(pcd));
+ }
+
+ return 0;
+}
+
+/**
+ * This function is called when the SRP timer expires. The SRP should complete
+ * within 6 seconds.
+ */
+static void srp_timeout(unsigned long data)
+{
+ u32 gotgctl;
+ struct dwc_pcd *pcd = (struct dwc_pcd *)data;
+ struct core_if *core_if = pcd->otg_dev->core_if;
+ ulong addr = otg_ctl_reg(pcd);
+
+ gotgctl = dwc_reg_read(addr, 0);
+ core_if->srp_timer_started = 0;
+
+ if (core_if->core_params->phy_type == DWC_PHY_TYPE_PARAM_FS &&
+ core_if->core_params->i2c_enable) {
+ pr_info("SRP Timeout\n");
+
+ if (core_if->srp_success && (gotgctl &
+ DWC_GCTL_BSESSION_VALID)) {
+ if (core_if->pcd_cb && core_if->pcd_cb->resume_wakeup)
+ core_if->pcd_cb->resume_wakeup(core_if->pcd_cb->
+ p);
+
+ /* Clear Session Request */
+ gotgctl = 0;
+ gotgctl |= DWC_GCTL_SES_REQ;
+ dwc_reg_modify(addr, 0, gotgctl, 0);
+
+ core_if->srp_success = 0;
+ } else {
+ pr_err("Device not connected/responding\n");
+ gotgctl &= ~DWC_GCTL_SES_REQ;
+ dwc_reg_write(addr, 0, gotgctl);
+ }
+ } else if (gotgctl & DWC_GCTL_SES_REQ) {
+ pr_info("SRP Timeout\n");
+ pr_err("Device not connected/responding\n");
+
+ gotgctl &= ~DWC_GCTL_SES_REQ;
+ dwc_reg_write(addr, 0, gotgctl);
+ } else {
+ pr_info(" SRP GOTGCTL=%0x\n", gotgctl);
+ }
+}
+
+/**
+ * Start the SRP timer to detect when the SRP does not complete within
+ * 6 seconds.
+ */
+static void dwc_otg_pcd_start_srp_timer(struct dwc_pcd *pcd)
+{
+ struct timer_list *srp_timer = &pcd->srp_timer;
+
+ GET_CORE_IF(pcd)->srp_timer_started = 1;
+ init_timer(srp_timer);
+ srp_timer->function = srp_timeout;
+ srp_timer->data = (unsigned long)pcd;
+ srp_timer->expires = jiffies + (HZ * 6);
+
+ add_timer(srp_timer);
+}
+
+static void dwc_otg_pcd_initiate_srp(struct dwc_pcd *pcd)
+{
+ u32 mem;
+ u32 val;
+ ulong addr = otg_ctl_reg(pcd);
+
+ val = dwc_reg_read(addr, 0);
+ if (val & DWC_GCTL_SES_REQ) {
+ pr_err("Session Request Already active!\n");
+ return;
+ }
+
+ pr_notice("Session Request Initated\n");
+ mem = dwc_reg_read(addr, 0);
+ mem |= DWC_GCTL_SES_REQ;
+ dwc_reg_write(addr, 0, mem);
+
+ /* Start the SRP timer */
+ dwc_otg_pcd_start_srp_timer(pcd);
+ return;
+}
+
+static void dwc_otg_pcd_remote_wakeup(struct dwc_pcd *pcd, int set)
+{
+ u32 dctl = 0;
+ ulong addr = dev_ctl_reg(pcd);
+
+ if (dwc_otg_is_device_mode(GET_CORE_IF(pcd))) {
+ if (pcd->remote_wakeup_enable) {
+ if (set) {
+ dctl = DEC_DCTL_REMOTE_WAKEUP_SIG(dctl, 1);
+ dwc_reg_modify(addr, 0, 0, dctl);
+ msleep(20);
+ dwc_reg_modify(addr, 0, dctl, 0);
+ }
+ }
+ }
+}
+
+/**
+ * Initiates Session Request Protocol (SRP) to wakeup the host if no
+ * session is in progress. If a session is already in progress, but
+ * the device is suspended, remote wakeup signaling is started.
+ *
+ */
+static int dwc_otg_pcd_wakeup(struct usb_gadget *_gadget)
+{
+ unsigned long flags;
+ struct dwc_pcd *pcd;
+ u32 dsts;
+ u32 gotgctl;
+
+ if (!_gadget)
+ return -ENODEV;
+ else
+ pcd = container_of(_gadget, struct dwc_pcd, gadget);
+
+ spin_lock_irqsave(&pcd->lock, flags);
+
+ /*
+ * This function starts the Protocol if no session is in progress. If
+ * a session is already in progress, but the device is suspended,
+ * remote wakeup signaling is started.
+ */
+
+ /* Check if valid session */
+ gotgctl = dwc_reg_read(otg_ctl_reg(pcd), 0);
+ if (gotgctl & DWC_GCTL_BSESSION_VALID) {
+ /* Check if suspend state */
+ dsts = dwc_reg_read(dev_sts_reg(pcd), 0);
+ if (DWC_DSTS_SUSP_STS_RD(dsts))
+ dwc_otg_pcd_remote_wakeup(pcd, 1);
+ } else {
+ dwc_otg_pcd_initiate_srp(pcd);
+ }
+
+ spin_unlock_irqrestore(&pcd->lock, flags);
+ return 0;
+}
+
+static int dwc_gadget_vbus_draw(struct usb_gadget *gadget, unsigned mA)
+{
+ struct dwc_pcd *pcd = container_of(gadget, struct dwc_pcd, gadget);
+
+ if (pcd->otg_dev->core_if->xceiv)
+ return -EOPNOTSUPP;
+
+ return otg_set_power(pcd->otg_dev->core_if->xceiv, mA);
+
+}
+static int dwc_gadget_start(struct usb_gadget_driver *driver,
+ int (*bind) (struct usb_gadget *));
+static int dwc_gadget_stop(struct usb_gadget_driver *driver);
+
+static const struct usb_gadget_ops dwc_otg_pcd_ops = {
+ .get_frame = dwc_otg_pcd_get_frame,
+ .wakeup = dwc_otg_pcd_wakeup,
+ .start = dwc_gadget_start,
+ .stop = dwc_gadget_stop,
+ .vbus_draw = dwc_gadget_vbus_draw,
+};
+
+/**
+ * This function updates the otg values in the gadget structure.
+ */
+void dwc_otg_pcd_update_otg(struct dwc_pcd *pcd, const unsigned reset)
+{
+ if (!pcd->gadget.is_otg)
+ return;
+
+ if (reset) {
+ pcd->b_hnp_enable = 0;
+ pcd->a_hnp_support = 0;
+ pcd->a_alt_hnp_support = 0;
+ }
+
+ pcd->gadget.b_hnp_enable = pcd->b_hnp_enable;
+ pcd->gadget.a_hnp_support = pcd->a_hnp_support;
+ pcd->gadget.a_alt_hnp_support = pcd->a_alt_hnp_support;
+}
+
+/**
+ * This function is the top level PCD interrupt handler.
+ */
+static irqreturn_t dwc_otg_pcd_irq(int _irq, void *dev)
+{
+ struct dwc_pcd *pcd = dev;
+ int retval;
+
+ retval = dwc_otg_pcd_handle_intr(pcd);
+ return IRQ_RETVAL(retval);
+}
+
+/**
+ * PCD Callback function for initializing the PCD when switching to
+ * device mode.
+ */
+static int dwc_otg_pcd_start_cb(void *_p)
+{
+ struct dwc_pcd *pcd = (struct dwc_pcd *)_p;
+
+ /* Initialize the Core for Device mode. */
+ if (dwc_otg_is_device_mode(GET_CORE_IF(pcd)))
+ dwc_otg_core_dev_init(GET_CORE_IF(pcd));
+
+ return 1;
+}
+
+/**
+ * PCD Callback function for stopping the PCD when switching to Host
+ * mode.
+ */
+static int dwc_otg_pcd_stop_cb(void *_p)
+{
+ dwc_otg_pcd_stop((struct dwc_pcd *)_p);
+ return 1;
+}
+
+/**
+ * PCD Callback function for notifying the PCD when resuming from
+ * suspend.
+ *
+ * @param _p void pointer to the <code>struct dwc_pcd</code>
+ */
+static int dwc_otg_pcd_suspend_cb(void *_p)
+{
+ struct dwc_pcd *pcd = (struct dwc_pcd *)_p;
+
+ if (pcd->driver && pcd->driver->suspend) {
+ spin_unlock(&pcd->lock);
+ pcd->driver->suspend(&pcd->gadget);
+ spin_lock(&pcd->lock);
+ }
+ return 1;
+}
+
+/**
+ * PCD Callback function for notifying the PCD when resuming from
+ * suspend.
+ */
+static int dwc_otg_pcd_resume_cb(void *_p)
+{
+ struct dwc_pcd *pcd = (struct dwc_pcd *)_p;
+ struct core_if *core_if = pcd->otg_dev->core_if;
+
+ if (pcd->driver && pcd->driver->resume) {
+ spin_unlock(&pcd->lock);
+ pcd->driver->resume(&pcd->gadget);
+ spin_lock(&pcd->lock);
+ }
+
+ /* Maybe stop the SRP timeout timer. */
+ if (need_stop_srp_timer(core_if)) {
+ core_if->srp_timer_started = 0;
+ del_timer_sync(&pcd->srp_timer);
+ }
+ return 1;
+}
+
+/**
+ * PCD Callback structure for handling mode switching.
+ */
+static struct cil_callbacks pcd_callbacks = {
+ .start = dwc_otg_pcd_start_cb,
+ .stop = dwc_otg_pcd_stop_cb,
+ .suspend = dwc_otg_pcd_suspend_cb,
+ .resume_wakeup = dwc_otg_pcd_resume_cb,
+ .p = NULL, /* Set at registration */
+};
+
+/**
+ * Tasklet
+ *
+ */
+static void start_xfer_tasklet_func(unsigned long data)
+{
+ struct dwc_pcd *pcd = (struct dwc_pcd *)data;
+ u32 diepctl = 0;
+ int num = pcd->otg_dev->core_if->dev_if->num_in_eps;
+ u32 i;
+ unsigned long flags;
+
+ spin_lock_irqsave(&pcd->lock, flags);
+ diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, 0), 0);
+
+ if (pcd->ep0.queue_sof) {
+ pcd->ep0.queue_sof = 0;
+ dwc_start_next_request(&pcd->ep0);
+ }
+
+ for (i = 0; i < num; i++) {
+ u32 diepctl = 0;
+
+ diepctl = dwc_reg_read(in_ep_ctl_reg(pcd, i), 0);
+ if (pcd->in_ep[i].queue_sof) {
+ pcd->in_ep[i].queue_sof = 0;
+ dwc_start_next_request(&pcd->in_ep[i]);
+ }
+ }
+ spin_unlock_irqrestore(&pcd->lock, flags);
+}
+
+static struct tasklet_struct start_xfer_tasklet = {
+ .next = NULL,
+ .state = 0,
+ .count = ATOMIC_INIT(0),
+ .func = start_xfer_tasklet_func,
+ .data = 0,
+};
+
+/**
+ * This function initialized the pcd Dp structures to there default
+ * state.
+ */
+static void __devinit dwc_otg_pcd_reinit(struct dwc_pcd *pcd)
+{
+ static const char *names[] = {
+ "ep0", "ep1in", "ep2in", "ep3in", "ep4in", "ep5in",
+ "ep6in", "ep7in", "ep8in", "ep9in", "ep10in", "ep11in",
+ "ep12in", "ep13in", "ep14in", "ep15in", "ep1out", "ep2out",
+ "ep3out", "ep4out", "ep5out", "ep6out", "ep7out", "ep8out",
+ "ep9out", "ep10out", "ep11out", "ep12out", "ep13out",
+ "ep14out", "ep15out"
+ };
+ u32 i;
+ int in_ep_cntr, out_ep_cntr;
+ u32 hwcfg1;
+ u32 num_in_eps = (GET_CORE_IF(pcd))->dev_if->num_in_eps;
+ u32 num_out_eps = (GET_CORE_IF(pcd))->dev_if->num_out_eps;
+ struct pcd_ep *ep;
+
+ INIT_LIST_HEAD(&pcd->gadget.ep_list);
+ pcd->gadget.ep0 = &pcd->ep0.ep;
+ pcd->gadget.speed = USB_SPEED_UNKNOWN;
+ INIT_LIST_HEAD(&pcd->gadget.ep0->ep_list);
+
+ /* Initialize the EP0 structure. */
+ ep = &pcd->ep0;
+
+ /* Init EP structure */
+ ep->desc = NULL;
+ ep->pcd = pcd;
+ ep->stopped = 1;
+
+ /* Init DWC ep structure */
+ ep->dwc_ep.num = 0;
+ ep->dwc_ep.active = 0;
+ ep->dwc_ep.tx_fifo_num = 0;
+
+ /* Control until ep is actvated */
+ ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL;
+ ep->dwc_ep.maxpacket = MAX_PACKET_SIZE;
+ ep->dwc_ep.dma_addr = 0;
+ ep->dwc_ep.start_xfer_buff = NULL;
+ ep->dwc_ep.xfer_buff = NULL;
+ ep->dwc_ep.xfer_len = 0;
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.sent_zlp = 0;
+ ep->dwc_ep.total_len = 0;
+ ep->queue_sof = 0;
+
+ /* Init the usb_ep structure. */
+ ep->ep.name = names[0];
+ ep->ep.ops = &dwc_otg_pcd_ep_ops;
+
+ ep->ep.maxpacket = MAX_PACKET_SIZE;
+ list_add_tail(&ep->ep.ep_list, &pcd->gadget.ep_list);
+ INIT_LIST_HEAD(&ep->queue);
+
+ /* Initialize the EP structures. */
+ in_ep_cntr = 0;
+ hwcfg1 = (GET_CORE_IF(pcd))->hwcfg1 >> 3;
+
+ for (i = 1; in_ep_cntr < num_in_eps; i++) {
+ if (!(hwcfg1 & 0x1)) {
+ struct pcd_ep *ep = &pcd->in_ep[in_ep_cntr];
+
+ in_ep_cntr++;
+ /* Init EP structure */
+ ep->desc = NULL;
+ ep->pcd = pcd;
+ ep->stopped = 1;
+
+ /* Init DWC ep structure */
+ ep->dwc_ep.is_in = 1;
+ ep->dwc_ep.num = i;
+ ep->dwc_ep.active = 0;
+ ep->dwc_ep.tx_fifo_num = 0;
+
+ /* Control until ep is actvated */
+ ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL;
+ ep->dwc_ep.maxpacket = MAX_PACKET_SIZE;
+ ep->dwc_ep.dma_addr = 0;
+ ep->dwc_ep.start_xfer_buff = NULL;
+ ep->dwc_ep.xfer_buff = NULL;
+ ep->dwc_ep.xfer_len = 0;
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.sent_zlp = 0;
+ ep->dwc_ep.total_len = 0;
+ ep->queue_sof = 0;
+
+ ep->ep.name = names[i];
+ ep->ep.ops = &dwc_otg_pcd_ep_ops;
+
+ ep->ep.maxpacket = MAX_PACKET_SIZE;
+ list_add_tail(&ep->ep.ep_list, &pcd->gadget.ep_list);
+ INIT_LIST_HEAD(&ep->queue);
+ }
+ hwcfg1 >>= 2;
+ }
+
+ out_ep_cntr = 0;
+ hwcfg1 = (GET_CORE_IF(pcd))->hwcfg1 >> 2;
+ for (i = 1; out_ep_cntr < num_out_eps; i++) {
+ if (!(hwcfg1 & 0x1)) {
+ struct pcd_ep *ep = &pcd->out_ep[out_ep_cntr];
+
+ out_ep_cntr++;
+ /* Init EP structure */
+ ep->desc = NULL;
+ ep->pcd = pcd;
+ ep->stopped = 1;
+
+ /* Init DWC ep structure */
+ ep->dwc_ep.is_in = 0;
+ ep->dwc_ep.num = i;
+ ep->dwc_ep.active = 0;
+ ep->dwc_ep.tx_fifo_num = 0;
+
+ /* Control until ep is actvated */
+ ep->dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL;
+ ep->dwc_ep.maxpacket = MAX_PACKET_SIZE;
+ ep->dwc_ep.dma_addr = 0;
+ ep->dwc_ep.start_xfer_buff = NULL;
+ ep->dwc_ep.xfer_buff = NULL;
+ ep->dwc_ep.xfer_len = 0;
+ ep->dwc_ep.xfer_count = 0;
+ ep->dwc_ep.sent_zlp = 0;
+ ep->dwc_ep.total_len = 0;
+ ep->queue_sof = 0;
+
+ ep->ep.name = names[15 + i];
+ ep->ep.ops = &dwc_otg_pcd_ep_ops;
+
+ ep->ep.maxpacket = MAX_PACKET_SIZE;
+ list_add_tail(&ep->ep.ep_list, &pcd->gadget.ep_list);
+ INIT_LIST_HEAD(&ep->queue);
+ }
+ hwcfg1 >>= 2;
+ }
+
+ /* remove ep0 from the list. There is a ep0 pointer. */
+ list_del_init(&pcd->ep0.ep.ep_list);
+
+ pcd->ep0state = EP0_DISCONNECT;
+ pcd->ep0.ep.maxpacket = MAX_EP0_SIZE;
+ pcd->ep0.dwc_ep.maxpacket = MAX_EP0_SIZE;
+ pcd->ep0.dwc_ep.type = DWC_OTG_EP_TYPE_CONTROL;
+}
+
+/**
+ * This function releases the Gadget device.
+ * required by device_unregister().
+ */
+static void dwc_otg_pcd_gadget_release(struct device *dev)
+{
+ pr_info("%s(%p)\n", __func__, dev);
+}
+
+/**
+ * Allocates the buffers for the setup packets when the PCD portion of the
+ * driver is first initialized.
+ */
+static int __devinit init_pkt_buffs(struct device *dev, struct dwc_pcd *pcd)
+{
+ if (pcd->otg_dev->core_if->dma_enable) {
+ pcd->dwc_pool = dma_pool_create("dwc_otg_pcd", dev,
+ sizeof(*pcd->setup_pkt) * 5, 32,
+ 0);
+ if (!pcd->dwc_pool)
+ return -ENOMEM;
+ pcd->setup_pkt = dma_pool_alloc(pcd->dwc_pool, GFP_KERNEL,
+ &pcd->setup_pkt_dma_handle);
+ if (!pcd->setup_pkt)
+ goto error;
+ pcd->status_buf = dma_pool_alloc(pcd->dwc_pool, GFP_KERNEL,
+ &pcd->status_buf_dma_handle);
+ if (!pcd->status_buf)
+ goto error1;
+ } else {
+ pcd->setup_pkt = kmalloc(sizeof(*pcd->setup_pkt) * 5,
+ GFP_KERNEL);
+ if (!pcd->setup_pkt)
+ return -ENOMEM;
+ pcd->status_buf = kmalloc(sizeof(u16), GFP_KERNEL);
+ if (!pcd->status_buf) {
+ kfree(pcd->setup_pkt);
+ return -ENOMEM;
+ }
+ }
+ return 0;
+
+error1:
+ dma_pool_free(pcd->dwc_pool, pcd->setup_pkt, pcd->setup_pkt_dma_handle);
+error:
+ dma_pool_destroy(pcd->dwc_pool);
+ return -ENOMEM;
+}
+
+/**
+ * This function initializes the PCD portion of the driver.
+ */
+int __devinit dwc_otg_pcd_init(struct device *dev)
+{
+ static const char pcd_name[] = "dwc_otg_pcd";
+ struct dwc_pcd *pcd;
+ struct dwc_otg_device *otg_dev = dev_get_drvdata(dev);
+ struct core_if *core_if = otg_dev->core_if;
+ int retval;
+
+ /* Allocate PCD structure */
+ pcd = kzalloc(sizeof(*pcd), GFP_KERNEL);
+ if (!pcd) {
+ retval = -ENOMEM;
+ goto err;
+ }
+
+ spin_lock_init(&pcd->lock);
+
+ otg_dev->pcd = pcd;
+ s_pcd = pcd;
+ pcd->gadget.name = pcd_name;
+
+ dev_set_name(&pcd->gadget.dev, "gadget");
+ pcd->otg_dev = otg_dev;
+ pcd->gadget.dev.parent = dev;
+ pcd->gadget.dev.release = dwc_otg_pcd_gadget_release;
+ pcd->gadget.ops = &dwc_otg_pcd_ops;
+
+ if (DWC_HWCFG4_DED_FIFO_ENA_RD(core_if->hwcfg4))
+ pr_info("Dedicated Tx FIFOs mode\n");
+ else
+ pr_info("Shared Tx FIFO mode\n");
+
+ pcd->gadget.is_dualspeed = check_is_dual_speed(core_if);
+ pcd->gadget.is_otg = check_is_otg(core_if);
+
+ /* Register the gadget device */
+ retval = device_register(&pcd->gadget.dev);
+ if (retval)
+ goto unreg_device;
+
+
+ /* hook up the gadget*/
+ retval = usb_add_gadget_udc(dev, &pcd->gadget);
+ if (retval)
+ goto unreg_device;
+
+ /* Initialized the Core for Device mode. */
+ if (dwc_otg_is_device_mode(core_if))
+ dwc_otg_core_dev_init(core_if);
+
+ /* Initialize EP structures */
+ dwc_otg_pcd_reinit(pcd);
+
+ /* Register the PCD Callbacks. */
+ dwc_otg_cil_register_pcd_callbacks(core_if, &pcd_callbacks, pcd);
+
+ /* Setup interupt handler */
+ retval = request_irq(otg_dev->irq, dwc_otg_pcd_irq, IRQF_SHARED,
+ pcd->gadget.name, pcd);
+ if (retval) {
+ pr_err("request of irq%d failed\n", otg_dev->irq);
+ retval = -EBUSY;
+ goto err_cleanup;
+ }
+
+ /* Initialize the DMA buffer for SETUP packets */
+ retval = init_pkt_buffs(dev, pcd);
+ if (retval)
+ goto err_cleanup;
+
+ /* Initialize tasklet */
+ start_xfer_tasklet.data = (unsigned long)pcd;
+ pcd->start_xfer_tasklet = &start_xfer_tasklet;
+ return 0;
+
+err_cleanup:
+ kfree(pcd);
+ otg_dev->pcd = NULL;
+ s_pcd = NULL;
+
+unreg_device:
+ device_unregister(&pcd->gadget.dev);
+
+err:
+ return retval;
+}
+
+/**
+ * Cleanup the PCD.
+ */
+void __devexit dwc_otg_pcd_remove(struct device *dev)
+{
+ struct dwc_otg_device *otg_dev = dev_get_drvdata(dev);
+ struct dwc_pcd *pcd = otg_dev->pcd;
+
+ /* Free the IRQ */
+ free_irq(otg_dev->irq, pcd);
+
+ /* start with the driver above us */
+ if (pcd->driver) {
+ /* should have been done already by driver model core */
+ pr_warning("driver '%s' is still registered\n",
+ pcd->driver->driver.name);
+ usb_gadget_unregister_driver(pcd->driver);
+ }
+ if (pcd->start_xfer_tasklet)
+ tasklet_kill(pcd->start_xfer_tasklet);
+ tasklet_kill(&pcd->test_mode_tasklet);
+
+ device_unregister(&pcd->gadget.dev);
+ if (GET_CORE_IF(pcd)->dma_enable) {
+ dma_pool_free(pcd->dwc_pool, pcd->setup_pkt,
+ pcd->setup_pkt_dma_handle);
+ dma_pool_free(pcd->dwc_pool, pcd->status_buf,
+ pcd->status_buf_dma_handle);
+ dma_pool_destroy(pcd->dwc_pool);
+ } else {
+ kfree(pcd->setup_pkt);
+ kfree(pcd->status_buf);
+ }
+ kfree(pcd);
+ otg_dev->pcd = NULL;
+}
+
+/**
+ * This function registers a gadget driver with the PCD.
+ *
+ * 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.
+ */
+static int dwc_gadget_start(struct usb_gadget_driver *driver,
+ int (*bind) (struct usb_gadget *))
+{
+ int retval;
+
+ if (!driver || driver->speed == USB_SPEED_UNKNOWN || !bind ||
+ !driver->unbind || !driver->disconnect || !driver->setup)
+ return -EINVAL;
+
+ if (s_pcd == NULL)
+ return -ENODEV;
+
+ if (s_pcd->driver != NULL)
+ return -EBUSY;
+
+ /* hook up the driver */
+ s_pcd->driver = driver;
+ s_pcd->gadget.dev.driver = &driver->driver;
+
+ retval = bind(&s_pcd->gadget);
+ if (retval) {
+ struct core_if *core_if;
+
+ pr_err("bind to driver %s --> error %d\n",
+ driver->driver.name, retval);
+ core_if = s_pcd->otg_dev->core_if;
+ otg_set_peripheral(core_if->xceiv, &s_pcd->gadget);
+ s_pcd->driver = NULL;
+ s_pcd->gadget.dev.driver = NULL;
+ return retval;
+ }
+ return 0;
+}
+
+/**
+ * This function unregisters a gadget driver
+ */
+static int dwc_gadget_stop(struct usb_gadget_driver *driver)
+{
+ struct core_if *core_if;
+
+ if (!s_pcd)
+ return -ENODEV;
+ if (!driver || driver != s_pcd->driver)
+ return -EINVAL;
+
+ core_if = s_pcd->otg_dev->core_if;
+ core_if->xceiv->state = OTG_STATE_UNDEFINED;
+ otg_set_peripheral(core_if->xceiv, NULL);
+
+ driver->unbind(&s_pcd->gadget);
+ s_pcd->driver = NULL;
+
+ return 0;
+}
diff --git a/drivers/usb/dwc/pcd.h b/drivers/usb/dwc/pcd.h
new file mode 100644
index 0000000..27c2d0d
--- /dev/null
+++ b/drivers/usb/dwc/pcd.h
@@ -0,0 +1,139 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#if !defined(__DWC_PCD_H__)
+#define __DWC_PCD_H__
+
+#include "driver.h"
+
+/*
+ * This file contains the structures, constants, and interfaces for
+ * the Perpherial Contoller Driver (PCD).
+ *
+ * The Peripheral Controller Driver (PCD) for Linux will implement the
+ * Gadget API, so that the existing Gadget drivers can be used. For
+ * the Mass Storage Function driver the File-backed USB Storage Gadget
+ * (FBS) driver will be used. The FBS driver supports the
+ * Control-Bulk (CB), Control-Bulk-Interrupt (CBI), and Bulk-Only
+ * transports.
+ *
+ */
+
+/* Invalid DMA Address */
+#define DMA_ADDR_INVALID (~(dma_addr_t) 0)
+/* Maxpacket size for EP0 */
+#define MAX_EP0_SIZE 64
+/* Maxpacket size for any EP */
+#define MAX_PACKET_SIZE 1024
+
+/*
+ * Get the pointer to the core_if from the pcd pointer.
+ */
+#define GET_CORE_IF(_pcd) (_pcd->otg_dev->core_if)
+
+/*
+ * DWC_otg request structure.
+ * This structure is a list of requests.
+ */
+struct pcd_request {
+ struct usb_request req; /* USB Request. */
+ struct list_head queue; /* queue of these requests. */
+ unsigned mapped:1;
+};
+
+static inline ulong in_ep_int_reg(struct dwc_pcd *pd, int i)
+{
+ return GET_CORE_IF(pd)->dev_if->in_ep_regs[i] + DWC_DIEPINT;
+}
+static inline ulong out_ep_int_reg(struct dwc_pcd *pd, int i)
+{
+ return GET_CORE_IF(pd)->dev_if->out_ep_regs[i] + DWC_DOEPINT;
+}
+static inline ulong in_ep_ctl_reg(struct dwc_pcd *pd, int i)
+{
+ return GET_CORE_IF(pd)->dev_if->in_ep_regs[i] + DWC_DIEPCTL;
+}
+
+static inline ulong out_ep_ctl_reg(struct dwc_pcd *pd, int i)
+{
+ return GET_CORE_IF(pd)->dev_if->out_ep_regs[i] + DWC_DOEPCTL;
+}
+
+static inline ulong dev_ctl_reg(struct dwc_pcd *pd)
+{
+ return GET_CORE_IF(pd)->dev_if->dev_global_regs +
+ DWC_DCTL;
+}
+
+static inline ulong dev_diepmsk_reg(struct dwc_pcd *pd)
+{
+ return GET_CORE_IF(pd)->dev_if->dev_global_regs +
+ DWC_DIEPMSK;
+}
+
+static inline ulong dev_sts_reg(struct dwc_pcd *pd)
+{
+ return GET_CORE_IF(pd)->dev_if->dev_global_regs +
+ DWC_DSTS;
+}
+
+static inline ulong otg_ctl_reg(struct dwc_pcd *pd)
+{
+ return GET_CORE_IF(pd)->core_global_regs + DWC_GOTGCTL;
+}
+
+extern int __init dwc_otg_pcd_init(struct device *dev);
+
+/*
+ * The following functions support managing the DWC_otg controller in device
+ * mode.
+ */
+extern void dwc_otg_ep_activate(struct core_if *core_if, struct dwc_ep *ep);
+extern void dwc_otg_ep_start_transfer(struct core_if *_if, struct dwc_ep *ep);
+extern void dwc_otg_ep_set_stall(struct core_if *core_if, struct dwc_ep *ep);
+extern void dwc_otg_ep_clear_stall(struct core_if *core_if, struct dwc_ep *ep);
+extern void dwc_otg_pcd_remove(struct device *dev);
+extern int dwc_otg_pcd_handle_intr(struct dwc_pcd *pcd);
+extern void dwc_otg_pcd_stop(struct dwc_pcd *pcd);
+extern void request_nuke(struct pcd_ep *ep);
+extern void dwc_otg_pcd_update_otg(struct dwc_pcd *pcd, const unsigned reset);
+extern void dwc_otg_ep0_start_transfer(struct core_if *_if, struct dwc_ep *ep);
+
+extern void request_done(struct pcd_ep *ep, struct pcd_request *req,
+ int _status);
+
+extern void dwc_start_next_request(struct pcd_ep *ep);
+#endif
--
1.7.0.4
^ permalink raw reply related
* [PATCH v16 06/10]USB/ppc4xx: Add Synopsys DWC OTG HCD queue function
From: Rupjyoti Sarmah @ 2012-05-03 12:35 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel; +Cc: rsarmah
Implements functions to manage Queue Heads and Queue
Transfer Descriptors of DWC USB OTG Controller.
Signed-off-by: Rupjyoti Sarmah <rsarmah@apm.com>
Signed-off-by: Tirumala R Marri <tmarri@apm.com>
Signed-off-by: Fushen Chen <fchen@apm.com>
Signed-off-by: Mark Miesfeld <mmiesfeld@apm.com>
---
drivers/usb/dwc/hcd_queue.c | 696 +++++++++++++++++++++++++++++++++++++++++++
1 files changed, 696 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/dwc/hcd_queue.c
diff --git a/drivers/usb/dwc/hcd_queue.c b/drivers/usb/dwc/hcd_queue.c
new file mode 100644
index 0000000..67f0409
--- /dev/null
+++ b/drivers/usb/dwc/hcd_queue.c
@@ -0,0 +1,696 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ * Modified by Chuck Meade <chuck@theptrgroup.com>
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/*
+ * This file contains the functions to manage Queue Heads and Queue
+ * Transfer Descriptors.
+ */
+
+#include "hcd.h"
+
+static inline int is_fs_ls(enum usb_device_speed speed)
+{
+ return speed == USB_SPEED_FULL || speed == USB_SPEED_LOW;
+}
+
+/* Allocates memory for a QH structure. */
+static inline struct dwc_qh *dwc_otg_hcd_qh_alloc(void)
+{
+ return kmalloc(sizeof(struct dwc_qh), GFP_ATOMIC);
+}
+
+/**
+ * Initializes a QH structure to initialize the QH.
+ */
+#define SCHEDULE_SLOP 10
+static void dwc_otg_hcd_qh_init(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ struct urb *urb)
+{
+ memset(qh, 0, sizeof(struct dwc_qh));
+
+ /* Initialize QH */
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_CONTROL:
+ qh->ep_type = USB_ENDPOINT_XFER_CONTROL;
+ break;
+ case PIPE_BULK:
+ qh->ep_type = USB_ENDPOINT_XFER_BULK;
+ break;
+ case PIPE_ISOCHRONOUS:
+ qh->ep_type = USB_ENDPOINT_XFER_ISOC;
+ break;
+ case PIPE_INTERRUPT:
+ qh->ep_type = USB_ENDPOINT_XFER_INT;
+ break;
+ }
+
+ qh->ep_is_in = usb_pipein(urb->pipe) ? 1 : 0;
+ qh->data_toggle = DWC_OTG_HC_PID_DATA0;
+ qh->maxp = usb_maxpacket(urb->dev, urb->pipe, !(usb_pipein(urb->pipe)));
+
+ INIT_LIST_HEAD(&qh->qtd_list);
+ INIT_LIST_HEAD(&qh->qh_list_entry);
+
+ qh->channel = NULL;
+ qh->speed = urb->dev->speed;
+
+ /*
+ * FS/LS Enpoint on HS Hub NOT virtual root hub
+ */
+ qh->do_split = 0;
+ if (is_fs_ls(urb->dev->speed) && urb->dev->tt && urb->dev->tt->hub &&
+ urb->dev->tt->hub->devnum != 1)
+ qh->do_split = 1;
+
+ if (qh->ep_type == USB_ENDPOINT_XFER_INT ||
+ qh->ep_type == USB_ENDPOINT_XFER_ISOC) {
+ /* Compute scheduling parameters once and save them. */
+ u32 hprt;
+ int bytecount = dwc_hb_mult(qh->maxp) *
+ dwc_max_packet(qh->maxp);
+
+ qh->usecs = NS_TO_US(usb_calc_bus_time(urb->dev->speed,
+ usb_pipein(urb->pipe),
+ (qh->ep_type ==
+ USB_ENDPOINT_XFER_ISOC),
+ bytecount));
+
+ /* Start in a slightly future (micro)frame. */
+ qh->sched_frame = dwc_frame_num_inc(hcd->frame_number,
+ SCHEDULE_SLOP);
+ qh->interval = urb->interval;
+
+ hprt = dwc_reg_read(hcd->core_if->host_if->hprt0, 0);
+ if (DWC_HPRT0_PRT_SPD_RD(hprt) == DWC_HPRT0_PRTSPD_HIGH_SPEED &&
+ is_fs_ls(urb->dev->speed)) {
+ qh->interval *= 8;
+ qh->sched_frame |= 0x7;
+ qh->start_split_frame = qh->sched_frame;
+ }
+ }
+}
+
+/**
+ * This function allocates and initializes a QH.
+ */
+static struct dwc_qh *dwc_otg_hcd_qh_create(struct dwc_hcd *hcd,
+ struct urb *urb)
+{
+ struct dwc_qh *qh;
+
+ /* Allocate memory */
+ qh = dwc_otg_hcd_qh_alloc();
+ if (qh == NULL)
+ return NULL;
+
+ dwc_otg_hcd_qh_init(hcd, qh, urb);
+ return qh;
+}
+
+/**
+ * Free each QTD in the QH's QTD-list then free the QH. QH should already be
+ * removed from a list. QTD list should already be empty if called from URB
+ * Dequeue.
+ */
+void dwc_otg_hcd_qh_free(struct dwc_qh *qh)
+{
+ struct dwc_qtd *qtd;
+ struct list_head *pos, *temp;
+
+ /* Free each QTD in the QTD list */
+ list_for_each_safe(pos, temp, &qh->qtd_list) {
+ list_del(pos);
+ qtd = dwc_list_to_qtd(pos);
+ dwc_otg_hcd_qtd_free(qtd);
+ }
+ kfree(qh);
+}
+
+/**
+ * Microframe scheduler
+ * track the total use in hcd->frame_usecs
+ * keep each qh use in qh->frame_usecs
+ * when surrendering the qh then donate the time back
+ */
+static const u16 max_uframe_usecs[] = { 100, 100, 100, 100, 100, 100, 30, 0 };
+
+/*
+ * called from dwc_otg_hcd.c:dwc_otg_hcd_init
+ */
+int init_hcd_usecs(struct dwc_hcd *hcd)
+{
+ int i;
+
+ for (i = 0; i < 8; i++)
+ hcd->frame_usecs[i] = max_uframe_usecs[i];
+
+ return 0;
+}
+
+static int find_single_uframe(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ int i;
+ u16 utime;
+ int t_left;
+ int ret;
+ int done;
+
+ ret = -1;
+ utime = qh->usecs;
+ t_left = utime;
+ i = 0;
+ done = 0;
+ while (done == 0) {
+ /* At the start hcd->frame_usecs[i] = max_uframe_usecs[i]; */
+ if (utime <= hcd->frame_usecs[i]) {
+ hcd->frame_usecs[i] -= utime;
+ qh->frame_usecs[i] += utime;
+ t_left -= utime;
+ ret = i;
+ done = 1;
+ return ret;
+ } else {
+ i++;
+ if (i == 8) {
+ done = 1;
+ ret = -1;
+ }
+ }
+ }
+ return ret;
+}
+
+/*
+ * use this for FS apps that can span multiple uframes
+ */
+static int find_multi_uframe(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ int i;
+ int j;
+ u16 utime;
+ int t_left;
+ int ret;
+ int done;
+ u16 xtime;
+
+ ret = -1;
+ utime = qh->usecs;
+ t_left = utime;
+ i = 0;
+ done = 0;
+loop:
+ while (done == 0) {
+ if (hcd->frame_usecs[i] <= 0) {
+ i++;
+ if (i == 8) {
+ done = 1;
+ ret = -1;
+ }
+ goto loop;
+ }
+
+ /*
+ * We need n consequtive slots so use j as a start slot.
+ * j plus j+1 must be enough time (for now)
+ */
+ xtime = hcd->frame_usecs[i];
+ for (j = i + 1; j < 8; j++) {
+ /*
+ * if we add this frame remaining time to xtime we may
+ * be OK, if not we need to test j for a complete frame.
+ */
+ if ((xtime + hcd->frame_usecs[j]) < utime) {
+ if (hcd->frame_usecs[j] < max_uframe_usecs[j]) {
+ j = 8;
+ ret = -1;
+ continue;
+ }
+ }
+ if (xtime >= utime) {
+ ret = i;
+ j = 8; /* stop loop with a good value ret */
+ continue;
+ }
+ /* add the frame time to x time */
+ xtime += hcd->frame_usecs[j];
+ /* we must have a fully available next frame or break */
+ if ((xtime < utime) &&
+ (hcd->frame_usecs[j] == max_uframe_usecs[j])) {
+ ret = -1;
+ j = 8; /* stop loop with a bad value ret */
+ continue;
+ }
+ }
+ if (ret >= 0) {
+ t_left = utime;
+ for (j = i; (t_left > 0) && (j < 8); j++) {
+ t_left -= hcd->frame_usecs[j];
+ if (t_left <= 0) {
+ qh->frame_usecs[j] +=
+ hcd->frame_usecs[j] + t_left;
+ hcd->frame_usecs[j] = -t_left;
+ ret = i;
+ done = 1;
+ } else {
+ qh->frame_usecs[j] +=
+ hcd->frame_usecs[j];
+ hcd->frame_usecs[j] = 0;
+ }
+ }
+ } else {
+ i++;
+ if (i == 8) {
+ done = 1;
+ ret = -1;
+ }
+ }
+ }
+ return ret;
+}
+
+static int find_uframe(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ int ret = -1;
+
+ if (qh->speed == USB_SPEED_HIGH)
+ /* if this is a hs transaction we need a full frame */
+ ret = find_single_uframe(hcd, qh);
+ else
+ /* FS transaction may need a sequence of frames */
+ ret = find_multi_uframe(hcd, qh);
+
+ return ret;
+}
+
+/**
+ * Checks that the max transfer size allowed in a host channel is large enough
+ * to handle the maximum data transfer in a single (micro)frame for a periodic
+ * transfer.
+ */
+static int check_max_xfer_size(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ int status = 0;
+ u32 max_xfer_size;
+ u32 max_channel_xfer_size;
+
+ max_xfer_size = dwc_max_packet(qh->maxp) * dwc_hb_mult(qh->maxp);
+ max_channel_xfer_size = hcd->core_if->core_params->max_transfer_size;
+
+ if (max_xfer_size > max_channel_xfer_size) {
+ pr_notice("%s: Periodic xfer length %d > max xfer "
+ "length for channel %d\n", __func__, max_xfer_size,
+ max_channel_xfer_size);
+ status = -ENOSPC;
+ }
+
+ return status;
+}
+
+/**
+ * Schedules an interrupt or isochronous transfer in the periodic schedule.
+ */
+static int schedule_periodic(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ int status;
+ struct usb_bus *bus = hcd_to_bus(dwc_otg_hcd_to_hcd(hcd));
+ int frame;
+
+ status = find_uframe(hcd, qh);
+ frame = -1;
+ if (status == 0) {
+ frame = 7;
+ } else {
+ if (status > 0)
+ frame = status - 1;
+ }
+ /* Set the new frame up */
+ if (frame > -1) {
+ qh->sched_frame &= ~0x7;
+ qh->sched_frame |= (frame & 7);
+ }
+ if (status != -1)
+ status = 0;
+ if (status) {
+ pr_notice("%s: Insufficient periodic bandwidth for "
+ "periodic transfer.\n", __func__);
+ return status;
+ }
+ status = check_max_xfer_size(hcd, qh);
+ if (status) {
+ pr_notice("%s: Channel max transfer size too small "
+ "for periodic transfer.\n", __func__);
+ return status;
+ }
+ /* Always start in the inactive schedule. */
+ list_add_tail(&qh->qh_list_entry, &hcd->periodic_sched_inactive);
+
+ /* Update claimed usecs per (micro)frame. */
+ hcd->periodic_usecs += qh->usecs;
+
+ /*
+ * Update average periodic bandwidth claimed and # periodic reqs for
+ * usbfs.
+ */
+ bus->bandwidth_allocated += qh->usecs / qh->interval;
+
+ if (qh->ep_type == USB_ENDPOINT_XFER_INT)
+ bus->bandwidth_int_reqs++;
+ else
+ bus->bandwidth_isoc_reqs++;
+
+ return status;
+}
+
+/**
+ * This function adds a QH to either the non periodic or periodic schedule if
+ * it is not already in the schedule. If the QH is already in the schedule, no
+ * action is taken.
+ */
+static int dwc_otg_hcd_qh_add(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ int status = 0;
+
+ /* QH may already be in a schedule. */
+ if (!list_empty(&qh->qh_list_entry))
+ goto done;
+ /*
+ * Add the new QH to the appropriate schedule. For non-periodic, always
+ * start in the inactive schedule.
+ */
+ if (dwc_qh_is_non_per(qh))
+ list_add_tail(&qh->qh_list_entry,
+ &hcd->non_periodic_sched_inactive);
+ else
+ status = schedule_periodic(hcd, qh);
+
+done:
+ return status;
+}
+
+/**
+ * This function adds a QH to the non periodic deferred schedule.
+ *
+ * @return 0 if successful, negative error code otherwise.
+ */
+static int dwc_otg_hcd_qh_add_deferred(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ if (!list_empty(&qh->qh_list_entry))
+ /* QH already in a schedule. */
+ goto done;
+
+ /* Add the new QH to the non periodic deferred schedule */
+ if (dwc_qh_is_non_per(qh))
+ list_add_tail(&qh->qh_list_entry,
+ &hcd->non_periodic_sched_deferred);
+done:
+ return 0;
+}
+
+/**
+ * Removes an interrupt or isochronous transfer from the periodic schedule.
+ */
+static void deschedule_periodic(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ struct usb_bus *bus = hcd_to_bus(dwc_otg_hcd_to_hcd(hcd));
+ int i;
+
+ list_del_init(&qh->qh_list_entry);
+ /* Update claimed usecs per (micro)frame. */
+ hcd->periodic_usecs -= qh->usecs;
+ for (i = 0; i < 8; i++) {
+ hcd->frame_usecs[i] += qh->frame_usecs[i];
+ qh->frame_usecs[i] = 0;
+ }
+ /*
+ * Update average periodic bandwidth claimed and # periodic reqs for
+ * usbfs.
+ */
+ bus->bandwidth_allocated -= qh->usecs / qh->interval;
+
+ if (qh->ep_type == USB_ENDPOINT_XFER_INT)
+ bus->bandwidth_int_reqs--;
+ else
+ bus->bandwidth_isoc_reqs--;
+}
+
+/**
+ * Removes a QH from either the non-periodic or periodic schedule. Memory is
+ * not freed.
+ */
+void dwc_otg_hcd_qh_remove(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ /* Do nothing if QH is not in a schedule */
+ if (list_empty(&qh->qh_list_entry))
+ return;
+
+ if (dwc_qh_is_non_per(qh)) {
+ if (hcd->non_periodic_qh_ptr == &qh->qh_list_entry)
+ hcd->non_periodic_qh_ptr =
+ hcd->non_periodic_qh_ptr->next;
+ list_del_init(&qh->qh_list_entry);
+ } else {
+ deschedule_periodic(hcd, qh);
+ }
+}
+
+/**
+ * Defers a QH. For non-periodic QHs, removes the QH from the active
+ * non-periodic schedule. The QH is added to the deferred non-periodic
+ * schedule if any QTDs are still attached to the QH.
+ */
+int dwc_otg_hcd_qh_deferr(struct dwc_hcd *hcd, struct dwc_qh *qh, int delay)
+{
+ int deact = 1;
+
+ if (dwc_qh_is_non_per(qh)) {
+ qh->sched_frame = dwc_frame_num_inc(hcd->frame_number, delay);
+ qh->channel = NULL;
+ qh->qtd_in_process = NULL;
+ deact = 0;
+ dwc_otg_hcd_qh_remove(hcd, qh);
+ if (!list_empty(&qh->qtd_list))
+ /* Add back to deferred non-periodic schedule. */
+ dwc_otg_hcd_qh_add_deferred(hcd, qh);
+ }
+ return deact;
+}
+
+/**
+ * Schedule the next continuing periodic split transfer
+ */
+static void sched_next_per_split_xfr(struct dwc_qh *qh, u16 fr_num,
+ int sched_split)
+{
+ if (sched_split) {
+ qh->sched_frame = fr_num;
+ if (dwc_frame_num_le(fr_num,
+ dwc_frame_num_inc(qh->start_split_frame,
+ 1))) {
+ /*
+ * Allow one frame to elapse after start split
+ * microframe before scheduling complete split, but DONT
+ * if we are doing the next start split in the
+ * same frame for an ISOC out.
+ */
+ if (qh->ep_type != USB_ENDPOINT_XFER_ISOC ||
+ qh->ep_is_in)
+ qh->sched_frame =
+ dwc_frame_num_inc(qh->sched_frame, 1);
+ }
+ } else {
+ qh->sched_frame = dwc_frame_num_inc(qh->start_split_frame,
+ qh->interval);
+
+ if (dwc_frame_num_le(qh->sched_frame, fr_num))
+ qh->sched_frame = fr_num;
+ qh->sched_frame |= 0x7;
+ qh->start_split_frame = qh->sched_frame;
+ }
+}
+
+/**
+ * Deactivates a periodic QH. The QH is removed from the periodic queued
+ * schedule. If there are any QTDs still attached to the QH, the QH is added to
+ * either the periodic inactive schedule or the periodic ready schedule and its
+ * next scheduled frame is calculated. The QH is placed in the ready schedule if
+ * the scheduled frame has been reached already. Otherwise it's placed in the
+ * inactive schedule. If there are no QTDs attached to the QH, the QH is
+ * completely removed from the periodic schedule.
+ */
+static void deactivate_periodic_qh(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ int sched_next_split)
+{
+ /* unsigned long flags; */
+ u16 fr_num = dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(hcd));
+
+ if (qh->do_split) {
+ sched_next_per_split_xfr(qh, fr_num, sched_next_split);
+ } else {
+ qh->sched_frame = dwc_frame_num_inc(qh->sched_frame,
+ qh->interval);
+ if (dwc_frame_num_le(qh->sched_frame, fr_num))
+ qh->sched_frame = fr_num;
+ }
+
+ if (list_empty(&qh->qtd_list)) {
+ dwc_otg_hcd_qh_remove(hcd, qh);
+ } else {
+ /*
+ * Remove from periodic_sched_queued and move to appropriate
+ * queue.
+ */
+ if (qh->sched_frame == fr_num)
+ list_move(&qh->qh_list_entry,
+ &hcd->periodic_sched_ready);
+ else
+ list_move(&qh->qh_list_entry,
+ &hcd->periodic_sched_inactive);
+ }
+}
+
+/**
+ * Deactivates a non-periodic QH. Removes the QH from the active non-periodic
+ * schedule. The QH is added to the inactive non-periodic schedule if any QTDs
+ * are still attached to the QH.
+ */
+static void deactivate_non_periodic_qh(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ dwc_otg_hcd_qh_remove(hcd, qh);
+ if (!list_empty(&qh->qtd_list))
+ dwc_otg_hcd_qh_add(hcd, qh);
+}
+
+/**
+ * Deactivates a QH. Determines if the QH is periodic or non-periodic and takes
+ * the appropriate action.
+ */
+void dwc_otg_hcd_qh_deactivate(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ int sched_next_periodic_split)
+{
+ if (dwc_qh_is_non_per(qh))
+ deactivate_non_periodic_qh(hcd, qh);
+ else
+ deactivate_periodic_qh(hcd, qh, sched_next_periodic_split);
+}
+
+/**
+ * Initializes a QTD structure.
+ */
+static void dwc_otg_hcd_qtd_init(struct dwc_qtd *qtd, struct urb *urb)
+{
+ memset(qtd, 0, sizeof(struct dwc_qtd));
+ qtd->urb = urb;
+
+ if (usb_pipecontrol(urb->pipe)) {
+ /*
+ * The only time the QTD data toggle is used is on the data
+ * phase of control transfers. This phase always starts with
+ * DATA1.
+ */
+ qtd->data_toggle = DWC_OTG_HC_PID_DATA1;
+ qtd->control_phase = DWC_OTG_CONTROL_SETUP;
+ }
+
+ /* start split */
+ qtd->complete_split = 0;
+ qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL;
+ qtd->isoc_split_offset = 0;
+
+ /* Store the qtd ptr in the urb to reference what QTD. */
+ urb->hcpriv = qtd;
+
+ INIT_LIST_HEAD(&qtd->qtd_list_entry);
+ return;
+}
+
+/* Allocates memory for a QTD structure. */
+static inline struct dwc_qtd *dwc_otg_hcd_qtd_alloc(gfp_t _mem_flags)
+{
+ return kmalloc(sizeof(struct dwc_qtd), _mem_flags);
+}
+
+/**
+ * This function allocates and initializes a QTD.
+ */
+struct dwc_qtd *dwc_otg_hcd_qtd_create(struct urb *urb, gfp_t _mem_flags)
+{
+ struct dwc_qtd *qtd = dwc_otg_hcd_qtd_alloc(_mem_flags);
+
+ if (!qtd)
+ return NULL;
+
+ dwc_otg_hcd_qtd_init(qtd, urb);
+ return qtd;
+}
+
+/**
+ * This function adds a QTD to the QTD-list of a QH. It will find the correct
+ * QH to place the QTD into. If it does not find a QH, then it will create a
+ * new QH. If the QH to which the QTD is added is not currently scheduled, it
+ * is placed into the proper schedule based on its EP type.
+ *
+ */
+int dwc_otg_hcd_qtd_add(struct dwc_qtd *qtd, struct dwc_hcd *hcd)
+{
+ struct usb_host_endpoint *ep;
+ struct dwc_qh *qh;
+ int retval = 0;
+ struct urb *urb = qtd->urb;
+
+ /*
+ * Get the QH which holds the QTD-list to insert to. Create QH if it
+ * doesn't exist.
+ */
+ ep = dwc_urb_to_endpoint(urb);
+
+ qh = (struct dwc_qh *)ep->hcpriv;
+ if (!qh) {
+ qh = dwc_otg_hcd_qh_create(hcd, urb);
+ if (!qh) {
+ retval = -1;
+ goto done;
+ }
+ ep->hcpriv = qh;
+ }
+ qtd->qtd_qh_ptr = qh;
+ retval = dwc_otg_hcd_qh_add(hcd, qh);
+ if (!retval)
+ list_add_tail(&qtd->qtd_list_entry, &qh->qtd_list);
+
+done:
+ return retval;
+}
--
1.7.0.4
^ permalink raw reply related
* [PATCH v16 05/10]USB/ppc4xx: Add Synopsys DWC OTG HCD interrupt function
From: Rupjyoti Sarmah @ 2012-05-03 12:34 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel; +Cc: rsarmah
FImplements DWC OTG USB HCD interrupt service routine.
Signed-off-by: Rupjyoti Sarmah <rsarmah@apm.com>
Signed-off-by: Tirumala R Marri <tmarri@apm.com>
Signed-off-by: Fushen Chen <fchen@apm.com>
Signed-off-by: Mark Miesfeld <mmiesfeld@apm.com>
---
drivers/usb/dwc/hcd_intr.c | 1472 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 1472 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/dwc/hcd_intr.c
diff --git a/drivers/usb/dwc/hcd_intr.c b/drivers/usb/dwc/hcd_intr.c
new file mode 100644
index 0000000..1239c78
--- /dev/null
+++ b/drivers/usb/dwc/hcd_intr.c
@@ -0,0 +1,1472 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ * Modified by Chuck Meade <chuck@theptrgroup.com>
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "hcd.h"
+
+/* This file contains the implementation of the HCD Interrupt handlers. */
+static const int erratum_usb09_patched;
+static const int deferral_on = 1;
+static const int nak_deferral_delay = 8;
+static const int nyet_deferral_delay = 1;
+
+/**
+ * Handles the start-of-frame interrupt in host mode. Non-periodic
+ * transactions may be queued to the DWC_otg controller for the current
+ * (micro)frame. Periodic transactions may be queued to the controller for the
+ * next (micro)frame.
+ */
+static int dwc_otg_hcd_handle_sof_intr(struct dwc_hcd *hcd)
+{
+ u32 hfnum;
+ struct list_head *qh_entry;
+ struct dwc_qh *qh;
+ enum dwc_transaction_type tr_type;
+ u32 gintsts = 0;
+
+ hfnum =
+ dwc_reg_read(hcd->core_if->host_if->host_global_regs,
+ DWC_HFNUM);
+
+ hcd->frame_number = DWC_HFNUM_FRNUM_RD(hfnum);
+
+ /* Determine whether any periodic QHs should be executed. */
+ qh_entry = hcd->periodic_sched_inactive.next;
+ while (qh_entry != &hcd->periodic_sched_inactive) {
+ qh = list_entry(qh_entry, struct dwc_qh, qh_list_entry);
+ qh_entry = qh_entry->next;
+
+ /*
+ * If needed, move QH to the ready list to be executed next
+ * (micro)frame.
+ */
+ if (dwc_frame_num_le(qh->sched_frame, hcd->frame_number))
+ list_move(&qh->qh_list_entry,
+ &hcd->periodic_sched_ready);
+ }
+
+ tr_type = dwc_otg_hcd_select_transactions(hcd);
+ if (tr_type != DWC_OTG_TRANSACTION_NONE)
+ dwc_otg_hcd_queue_transactions(hcd, tr_type);
+
+ /* Clear interrupt */
+ gintsts |= DWC_INTMSK_STRT_OF_FRM;
+ dwc_reg_write(gintsts_reg(hcd), 0, gintsts);
+ return 1;
+}
+
+/**
+ * Handles the Rx Status Queue Level Interrupt, which indicates that there is at
+ * least one packet in the Rx FIFO. The packets are moved from the FIFO to
+ * memory if the DWC_otg controller is operating in Slave mode.
+ */
+static int dwc_otg_hcd_handle_rx_status_q_level_intr(struct dwc_hcd *hcd)
+{
+ u32 grxsts;
+ struct dwc_hc *hc;
+
+ grxsts = dwc_reg_read(hcd->core_if->core_global_regs, DWC_GRXSTSP);
+ hc = hcd->hc_ptr_array[grxsts & DWC_HM_RXSTS_CHAN_NUM_RD(grxsts)];
+
+ /* Packet Status */
+ switch (DWC_HM_RXSTS_PKT_STS_RD(grxsts)) {
+ case DWC_GRXSTS_PKTSTS_IN:
+ /* Read the data into the host buffer. */
+ if (DWC_HM_RXSTS_BYTE_CNT_RD(grxsts) > 0) {
+ dwc_otg_read_packet(hcd->core_if, hc->xfer_buff,
+ DWC_HM_RXSTS_BYTE_CNT_RD(grxsts));
+ /* Update the HC fields for the next packet received. */
+ hc->xfer_count += DWC_HM_RXSTS_BYTE_CNT_RD(grxsts);
+ hc->xfer_buff += DWC_HM_RXSTS_BYTE_CNT_RD(grxsts);
+ }
+ case DWC_GRXSTS_PKTSTS_IN_XFER_COMP:
+ case DWC_GRXSTS_PKTSTS_DATA_TOGGLE_ERR:
+ case DWC_GRXSTS_PKTSTS_CH_HALTED:
+ /* Handled in interrupt, just ignore data */
+ break;
+ default:
+ pr_err("RX_STS_Q Interrupt: Unknown status %d\n",
+ DWC_HM_RXSTS_PKT_STS_RD(grxsts));
+ break;
+ }
+ return 1;
+}
+
+/**
+ * This interrupt occurs when the non-periodic Tx FIFO is half-empty. More
+ * data packets may be written to the FIFO for OUT transfers. More requests
+ * may be written to the non-periodic request queue for IN transfers. This
+ * interrupt is enabled only in Slave mode.
+ */
+static int dwc_otg_hcd_handle_np_tx_fifo_empty_intr(struct dwc_hcd *hcd)
+{
+ dwc_otg_hcd_queue_transactions(hcd, DWC_OTG_TRANSACTION_NON_PERIODIC);
+ return 1;
+}
+
+/**
+ * This interrupt occurs when the periodic Tx FIFO is half-empty. More data
+ * packets may be written to the FIFO for OUT transfers. More requests may be
+ * written to the periodic request queue for IN transfers. This interrupt is
+ * enabled only in Slave mode.
+ */
+static int dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(struct dwc_hcd *hcd)
+{
+ dwc_otg_hcd_queue_transactions(hcd, DWC_OTG_TRANSACTION_PERIODIC);
+ return 1;
+}
+
+/**
+ * When the port changes to enabled it may be necessary to adjust the phy clock
+ * speed.
+ */
+static int adjusted_phy_clock_speed(struct dwc_hcd *hcd, u32 hprt0)
+{
+ int adjusted = 0;
+ u32 usbcfg;
+ ulong global_regs = hcd->core_if->core_global_regs;
+ struct core_params *params = hcd->core_if->core_params;
+ ulong h_regs = hcd->core_if->host_if->host_global_regs;
+
+ usbcfg = dwc_reg_read(global_regs, DWC_GUSBCFG);
+
+ if (DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_LOW_SPEED ||
+ DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_FULL_SPEED) {
+ /* Low power */
+ u32 hcfg;
+
+ if (!(usbcfg & DWC_USBCFG_PHYLPWRCLKSEL)) {
+ /* Set PHY low power clock select for FS/LS devices */
+ usbcfg |= DWC_USBCFG_PHYLPWRCLKSEL;
+ dwc_reg_write(global_regs, DWC_GUSBCFG, usbcfg);
+ adjusted = 1;
+ }
+
+ hcfg = dwc_reg_read(h_regs, DWC_HCFG);
+ if (DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_LOW_SPEED &&
+ params->host_ls_low_power_phy_clk ==
+ DWC_HOST_LS_LOW_POWER_PHY_CLK_PARAM_6MHZ) {
+ /* 6 MHZ, check for 6 MHZ clock select */
+ if (DWC_HCFG_FSLSP_CLK_RD(hcfg) != DWC_HCFG_6_MHZ) {
+ hcfg = DWC_HCFG_FSLSP_CLK_RW(hcfg,
+ DWC_HCFG_6_MHZ);
+ dwc_reg_write(h_regs, DWC_HCFG, hcfg);
+ adjusted = 1;
+ }
+ } else if (DWC_HCFG_FSLSP_CLK_RD(hcfg) != DWC_HCFG_48_MHZ) {
+ /* 48 MHZ and clock select is not 48 MHZ */
+ hcfg = DWC_HCFG_FSLSP_CLK_RW(hcfg, DWC_HCFG_48_MHZ);
+ dwc_reg_write(h_regs, DWC_HCFG, hcfg);
+ adjusted = 1;
+ }
+ } else if (usbcfg & DWC_USBCFG_PHYLPWRCLKSEL) {
+ usbcfg &= ~((u32) DWC_USBCFG_PHYLPWRCLKSEL);
+ dwc_reg_write(global_regs, DWC_GUSBCFG, usbcfg);
+ adjusted = 1;
+ }
+ if (adjusted)
+ schedule_work(&hcd->usb_port_reset);
+
+ return adjusted;
+}
+
+/**
+ * Helper function to handle the port enable changed interrupt when the port
+ * becomes enabled. Checks if we need to adjust the PHY clock speed for low
+ * power and adjusts it if needed.
+ */
+static void port_enabled(struct dwc_hcd *hcd, u32 hprt0)
+{
+ if (hcd->core_if->core_params->host_support_fs_ls_low_power)
+ if (!adjusted_phy_clock_speed(hcd, hprt0))
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0);
+ dwc_reg_write(hcd->core_if->host_if->hprt0, 0, hprt0);
+}
+
+/**
+ * There are multiple conditions that can cause a port interrupt. This function
+ * determines which interrupt conditions have occurred and handles them
+ * appropriately.
+ */
+static int dwc_otg_hcd_handle_port_intr(struct dwc_hcd *hcd)
+{
+ int retval = 0;
+ u32 hprt0;
+ u32 hprt0_modify;
+
+ hprt0 = dwc_reg_read(hcd->core_if->host_if->hprt0, 0);
+ hprt0_modify = dwc_reg_read(hcd->core_if->host_if->hprt0, 0);
+
+ /*
+ * Clear appropriate bits in HPRT0 to clear the interrupt bit in
+ * GINTSTS
+ */
+ hprt0_modify = DWC_HPRT0_PRT_ENA_RW(hprt0_modify, 0);
+ hprt0_modify = DWC_HPRT0_PRT_CONN_DET_RW(hprt0_modify, 0);
+ hprt0_modify = DWC_HPRT0_PRT_ENA_DIS_CHG_RW(hprt0_modify, 0);
+ hprt0_modify = DWC_HPRT0_PRT_OVRCURR_CHG_RW(hprt0_modify, 0);
+
+ /* Port connect detected interrupt */
+ if (DWC_HPRT0_PRT_CONN_DET_RD(hprt0)) {
+ /* Set the status flags and clear interrupt */
+ hprt0_modify = DWC_HPRT0_PRT_CONN_DET_RW(hprt0_modify, 1);
+
+ /* B-Device has connected, Delete the connection timer. */
+ del_timer_sync(&hcd->conn_timer);
+
+ /*
+ * The Hub driver asserts a reset when it sees port connect
+ * status change flag
+ */
+ retval |= 1;
+ }
+
+ /* Port enable changed interrupt */
+ if (DWC_HPRT0_PRT_ENA_DIS_CHG_RD(hprt0)) {
+ /* Set the internal flag if the port was disabled */
+ if (DWC_HPRT0_PRT_ENA_RD(hprt0))
+ port_enabled(hcd, hprt0);
+ /* Clear the interrupt */
+ hprt0_modify = DWC_HPRT0_PRT_ENA_DIS_CHG_RW(hprt0_modify, 1);
+ retval |= 1;
+ }
+
+ /* Overcurrent change interrupt */
+ if (DWC_HPRT0_PRT_OVRCURR_CHG_RD(hprt0)) {
+ hprt0_modify = DWC_HPRT0_PRT_OVRCURR_CHG_RW(hprt0_modify, 1);
+ retval |= 1;
+ }
+
+ /* Clear the port interrupts */
+ dwc_reg_write(hcd->core_if->host_if->hprt0, 0, hprt0_modify);
+ return retval;
+}
+
+/**
+ * Gets the actual length of a transfer after the transfer halts. halt_status
+ * holds the reason for the halt.
+ *
+ * For IN transfers where halt_status is DWC_OTG_HC_XFER_COMPLETE, _short_read
+ * is set to 1 upon return if less than the requested number of bytes were
+ * transferred. Otherwise, _short_read is set to 0 upon return. _short_read may
+ * also be NULL on entry, in which case it remains unchanged.
+ */
+static u32 get_actual_xfer_length(struct dwc_hc *hc, ulong regs,
+ struct dwc_qtd *qtd,
+ enum dwc_halt_status halt_status,
+ int *_short_read)
+{
+ u32 hctsiz = 0;
+ u32 length;
+
+ if (_short_read)
+ *_short_read = 0;
+
+ hctsiz = dwc_reg_read(regs, DWC_HCTSIZ);
+ if (halt_status == DWC_OTG_HC_XFER_COMPLETE) {
+ if (hc->ep_is_in) {
+ length = hc->xfer_len - DWC_HCTSIZ_XFER_SIZE_RD(hctsiz);
+ if (_short_read)
+ *_short_read =
+ (DWC_HCTSIZ_XFER_SIZE_RD(hctsiz) != 0);
+ } else if (hc->qh->do_split) {
+ length = qtd->ssplit_out_xfer_count;
+ } else {
+ length = hc->xfer_len;
+ }
+ } else {
+ /*
+ * Must use the hctsiz.pktcnt field to determine how much data
+ * has been transferred. This field reflects the number of
+ * packets that have been transferred via the USB. This is
+ * always an integral number of packets if the transfer was
+ * halted before its normal completion. (Can't use the
+ * hctsiz.xfersize field because that reflects the number of
+ * bytes transferred via the AHB, not the USB).
+ */
+ length = (hc->start_pkt_count - DWC_HCTSIZ_PKT_CNT_RD(hctsiz)) *
+ hc->max_packet;
+ }
+ return length;
+}
+
+/**
+ * Updates the state of the URB after a Transfer Complete interrupt on the
+ * host channel. Updates the actual_length field of the URB based on the
+ * number of bytes transferred via the host channel. Sets the URB status
+ * if the data transfer is finished.
+ */
+static int update_urb_state_xfer_comp(struct dwc_hc *hc,
+ ulong regs, struct urb *urb,
+ struct dwc_qtd *qtd, int *status)
+{
+ int xfer_done = 0;
+ int short_read = 0;
+
+ urb->actual_length += get_actual_xfer_length(hc, regs, qtd,
+ DWC_OTG_HC_XFER_COMPLETE,
+ &short_read);
+
+ if (short_read || urb->actual_length == urb->transfer_buffer_length) {
+ xfer_done = 1;
+ if (short_read && (urb->transfer_flags & URB_SHORT_NOT_OK))
+ *status = -EREMOTEIO;
+ else
+ *status = 0;
+ }
+ return xfer_done;
+}
+
+/*
+ * Save the starting data toggle for the next transfer. The data toggle is
+ * saved in the QH for non-control transfers and it's saved in the QTD for
+ * control transfers.
+ */
+static void save_data_toggle(struct dwc_hc *hc, ulong regs, struct dwc_qtd *qtd)
+{
+ u32 hctsiz = 0;
+ hctsiz = dwc_reg_read(regs, DWC_HCTSIZ);
+
+ if (hc->ep_type != DWC_OTG_EP_TYPE_CONTROL) {
+ struct dwc_qh *qh = hc->qh;
+
+ if (DWC_HCTSIZ_PKT_PID_RD(hctsiz) == DWC_HCTSIZ_DATA0)
+ qh->data_toggle = DWC_OTG_HC_PID_DATA0;
+ else
+ qh->data_toggle = DWC_OTG_HC_PID_DATA1;
+ } else {
+ if (DWC_HCTSIZ_PKT_PID_RD(hctsiz) == DWC_HCTSIZ_DATA0)
+ qtd->data_toggle = DWC_OTG_HC_PID_DATA0;
+ else
+ qtd->data_toggle = DWC_OTG_HC_PID_DATA1;
+ }
+}
+
+/**
+ * Frees the first QTD in the QH's list if free_qtd is 1. For non-periodic
+ * QHs, removes the QH from the active non-periodic schedule. If any QTDs are
+ * still linked to the QH, the QH is added to the end of the inactive
+ * non-periodic schedule. For periodic QHs, removes the QH from the periodic
+ * schedule if no more QTDs are linked to the QH.
+ */
+static void deactivate_qh(struct dwc_hcd *hcd, struct dwc_qh *qh, int free_qtd)
+{
+ int continue_split = 0;
+ struct dwc_qtd *qtd;
+
+ qtd = list_entry(qh->qtd_list.next, struct dwc_qtd, qtd_list_entry);
+ if (qtd->complete_split)
+ continue_split = 1;
+ else if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_MID ||
+ qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_END)
+ continue_split = 1;
+
+ if (free_qtd) {
+ dwc_otg_hcd_qtd_remove(qtd);
+ continue_split = 0;
+ }
+
+ qh->channel = NULL;
+ qh->qtd_in_process = NULL;
+ dwc_otg_hcd_qh_deactivate(hcd, qh, continue_split);
+}
+
+/**
+ * Updates the state of an Isochronous URB when the transfer is stopped for
+ * any reason. The fields of the current entry in the frame descriptor array
+ * are set based on the transfer state and the input status. Completes the
+ * Isochronous URB if all the URB frames have been completed.
+ */
+static enum dwc_halt_status update_isoc_urb_state(struct dwc_hcd *hcd,
+ struct dwc_hc *hc, u32 regs,
+ struct dwc_qtd *qtd,
+ enum dwc_halt_status status)
+{
+ struct urb *urb = qtd->urb;
+ enum dwc_halt_status ret_val = status;
+ struct usb_iso_packet_descriptor *frame_desc;
+ frame_desc = &urb->iso_frame_desc[qtd->isoc_frame_index];
+
+ switch (status) {
+ case DWC_OTG_HC_XFER_COMPLETE:
+ frame_desc->status = 0;
+ frame_desc->actual_length =
+ get_actual_xfer_length(hc, regs, qtd, status, NULL);
+ break;
+ case DWC_OTG_HC_XFER_FRAME_OVERRUN:
+ urb->error_count++;
+ if (hc->ep_is_in)
+ frame_desc->status = -ENOSR;
+ else
+ frame_desc->status = -ECOMM;
+
+ frame_desc->actual_length = 0;
+ break;
+ case DWC_OTG_HC_XFER_BABBLE_ERR:
+ /* Don't need to update actual_length in this case. */
+ urb->error_count++;
+ frame_desc->status = -EOVERFLOW;
+ break;
+ case DWC_OTG_HC_XFER_XACT_ERR:
+ urb->error_count++;
+ frame_desc->status = -EPROTO;
+ frame_desc->actual_length =
+ get_actual_xfer_length(hc, regs, qtd, status, NULL);
+ default:
+ pr_err("%s: Unhandled halt_status (%d)\n", __func__, status);
+ BUG();
+ break;
+ }
+
+ if (++qtd->isoc_frame_index == urb->number_of_packets) {
+ /*
+ * urb->status is not used for isoc transfers.
+ * The individual frame_desc statuses are used instead.
+ */
+ dwc_otg_hcd_complete_urb(hcd, urb, 0);
+ ret_val = DWC_OTG_HC_XFER_URB_COMPLETE;
+ } else {
+ ret_val = DWC_OTG_HC_XFER_COMPLETE;
+ }
+ return ret_val;
+}
+
+/**
+ * Releases a host channel for use by other transfers. Attempts to select and
+ * queue more transactions since at least one host channel is available.
+ */
+static void release_channel(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ struct dwc_qtd *qtd,
+ enum dwc_halt_status halt_status, int *must_free)
+{
+ enum dwc_transaction_type tr_type;
+ int free_qtd;
+ int deact = 1;
+ struct dwc_qh *qh;
+ int retry_delay = 1;
+
+ switch (halt_status) {
+ case DWC_OTG_HC_XFER_NYET:
+ case DWC_OTG_HC_XFER_NAK:
+ if (halt_status == DWC_OTG_HC_XFER_NYET)
+ retry_delay = nyet_deferral_delay;
+ else
+ retry_delay = nak_deferral_delay;
+ free_qtd = 0;
+ if (deferral_on && hc->do_split) {
+ qh = hc->qh;
+ if (qh)
+ deact = dwc_otg_hcd_qh_deferr(hcd, qh,
+ retry_delay);
+ }
+ break;
+ case DWC_OTG_HC_XFER_URB_COMPLETE:
+ free_qtd = 1;
+ break;
+ case DWC_OTG_HC_XFER_AHB_ERR:
+ case DWC_OTG_HC_XFER_STALL:
+ case DWC_OTG_HC_XFER_BABBLE_ERR:
+ free_qtd = 1;
+ break;
+ case DWC_OTG_HC_XFER_XACT_ERR:
+ if (qtd->error_count >= 3) {
+ free_qtd = 1;
+ dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EPROTO);
+ } else {
+ free_qtd = 0;
+ }
+ break;
+ case DWC_OTG_HC_XFER_URB_DEQUEUE:
+ /*
+ * The QTD has already been removed and the QH has been
+ * deactivated. Don't want to do anything except release the
+ * host channel and try to queue more transfers.
+ */
+ goto cleanup;
+ case DWC_OTG_HC_XFER_NO_HALT_STATUS:
+ pr_err("%s: No halt_status, channel %d\n", __func__,
+ hc->hc_num);
+ free_qtd = 0;
+ break;
+ default:
+ free_qtd = 0;
+ break;
+ }
+ if (free_qtd)
+ /* must_free pre-initialized to zero */
+ *must_free = 1;
+ if (deact)
+ deactivate_qh(hcd, hc->qh, free_qtd);
+
+cleanup:
+ /*
+ * Release the host channel for use by other transfers. The cleanup
+ * function clears the channel interrupt enables and conditions, so
+ * there's no need to clear the Channel Halted interrupt separately.
+ */
+ dwc_otg_hc_cleanup(hcd->core_if, hc);
+ list_add_tail(&hc->hc_list_entry, &hcd->free_hc_list);
+ hcd->available_host_channels++;
+ /* Try to queue more transfers now that there's a free channel. */
+ if (!erratum_usb09_patched) {
+ tr_type = dwc_otg_hcd_select_transactions(hcd);
+ if (tr_type != DWC_OTG_TRANSACTION_NONE)
+ dwc_otg_hcd_queue_transactions(hcd, tr_type);
+ }
+}
+
+/**
+ * Halts a host channel. If the channel cannot be halted immediately because
+ * the request queue is full, this function ensures that the FIFO empty
+ * interrupt for the appropriate queue is enabled so that the halt request can
+ * be queued when there is space in the request queue.
+ *
+ * This function may also be called in DMA mode. In that case, the channel is
+ * simply released since the core always halts the channel automatically in
+ * DMA mode.
+ */
+static void halt_channel(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ struct dwc_qtd *qtd, enum dwc_halt_status halt_status,
+ int *must_free)
+{
+ if (hcd->core_if->dma_enable) {
+ release_channel(hcd, hc, qtd, halt_status, must_free);
+ return;
+ }
+
+ /* Slave mode processing... */
+ dwc_otg_hc_halt(hcd->core_if, hc, halt_status);
+ if (hc->halt_on_queue) {
+ u32 gintmsk = 0;
+
+ if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL ||
+ hc->ep_type == DWC_OTG_EP_TYPE_BULK) {
+ /*
+ * Make sure the Non-periodic Tx FIFO empty interrupt
+ * is enabled so that the non-periodic schedule will
+ * be processed.
+ */
+ gintmsk |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(gintmsk_reg(hcd), 0, 0, gintmsk);
+ } else {
+ /*
+ * Move the QH from the periodic queued schedule to
+ * the periodic assigned schedule. This allows the
+ * halt to be queued when the periodic schedule is
+ * processed.
+ */
+ list_move(&hc->qh->qh_list_entry,
+ &hcd->periodic_sched_assigned);
+
+ /*
+ * Make sure the Periodic Tx FIFO Empty interrupt is
+ * enabled so that the periodic schedule will be
+ * processed.
+ */
+ gintmsk |= DWC_INTMSK_P_TXFIFO_EMPTY;
+ dwc_reg_modify(gintmsk_reg(hcd), 0, 0, gintmsk);
+ }
+ }
+}
+
+/**
+ * Performs common cleanup for non-periodic transfers after a Transfer
+ * Complete interrupt. This function should be called after any endpoint type
+ * specific handling is finished to release the host channel.
+ */
+static void complete_non_periodic_xfer(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ ulong regs, struct dwc_qtd *qtd,
+ enum dwc_halt_status halt_status,
+ int *must_free)
+{
+ u32 hcint;
+
+ qtd->error_count = 0;
+ hcint = dwc_reg_read(regs, DWC_HCINT);
+ if (DWC_HCINT_NYET_RESP_REC_RD(hcint)) {
+ u32 hcint_clear = 0;
+
+ hcint_clear = DWC_HCINT_NYET_RESP_REC_RW(hcint_clear, 1);
+ /*
+ * Got a NYET on the last transaction of the transfer. This
+ * means that the endpoint should be in the PING state at the
+ * beginning of the next transfer.
+ */
+ hc->qh->ping_state = 1;
+ dwc_reg_write(regs, DWC_HCINT, hcint_clear);
+ }
+
+ /*
+ * Always halt and release the host channel to make it available for
+ * more transfers. There may still be more phases for a control
+ * transfer or more data packets for a bulk transfer at this point,
+ * but the host channel is still halted. A channel will be reassigned
+ * to the transfer when the non-periodic schedule is processed after
+ * the channel is released. This allows transactions to be queued
+ * properly via dwc_otg_hcd_queue_transactions, which also enables the
+ * Tx FIFO Empty interrupt if necessary.
+ *
+ * IN transfers in Slave mode require an explicit disable to
+ * halt the channel. (In DMA mode, this call simply releases
+ * the channel.)
+ *
+ * The channel is automatically disabled by the core for OUT
+ * transfers in Slave mode.
+ */
+ if (hc->ep_is_in)
+ halt_channel(hcd, hc, qtd, halt_status, must_free);
+ else
+ release_channel(hcd, hc, qtd, halt_status, must_free);
+}
+
+/**
+ * Performs common cleanup for periodic transfers after a Transfer Complete
+ * interrupt. This function should be called after any endpoint type specific
+ * handling is finished to release the host channel.
+ */
+static void complete_periodic_xfer(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ ulong regs, struct dwc_qtd *qtd,
+ enum dwc_halt_status halt_status,
+ int *must_free)
+{
+ u32 hctsiz = 0;
+
+ hctsiz = dwc_reg_read(regs, DWC_HCTSIZ);
+ qtd->error_count = 0;
+
+ /*
+ * For OUT transfers and 0 packet count, the Core halts the channel,
+ * otherwise, Flush any outstanding requests from the Tx queue.
+ */
+ if (!hc->ep_is_in || (DWC_HCTSIZ_PKT_CNT_RD(hctsiz) == 0))
+ release_channel(hcd, hc, qtd, halt_status, must_free);
+ else
+ halt_channel(hcd, hc, qtd, halt_status, must_free);
+}
+
+/**
+ * Handles a host channel Transfer Complete interrupt. This handler may be
+ * called in either DMA mode or Slave mode.
+ */
+static int handle_hc_xfercomp_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ ulong regs, struct dwc_qtd *qtd,
+ int *must_free)
+{
+ int urb_xfer_done;
+ enum dwc_halt_status halt_status = DWC_OTG_HC_XFER_COMPLETE;
+ struct urb *urb = qtd->urb;
+ int pipe_type = usb_pipetype(urb->pipe);
+ int status = -EINPROGRESS;
+ u32 hcintmsk = 0;
+
+ /* Handle xfer complete on CSPLIT. */
+ if (hc->qh->do_split)
+ qtd->complete_split = 0;
+
+ /* Update the QTD and URB states. */
+ switch (pipe_type) {
+ case PIPE_CONTROL:
+ switch (qtd->control_phase) {
+ case DWC_OTG_CONTROL_SETUP:
+ if (urb->transfer_buffer_length > 0)
+ qtd->control_phase = DWC_OTG_CONTROL_DATA;
+ else
+ qtd->control_phase = DWC_OTG_CONTROL_STATUS;
+ halt_status = DWC_OTG_HC_XFER_COMPLETE;
+ break;
+ case DWC_OTG_CONTROL_DATA:
+ urb_xfer_done = update_urb_state_xfer_comp(hc, regs,
+ urb, qtd,
+ &status);
+ if (urb_xfer_done)
+ qtd->control_phase = DWC_OTG_CONTROL_STATUS;
+ else
+ save_data_toggle(hc, regs, qtd);
+ halt_status = DWC_OTG_HC_XFER_COMPLETE;
+ break;
+ case DWC_OTG_CONTROL_STATUS:
+ if (status == -EINPROGRESS)
+ status = 0;
+ dwc_otg_hcd_complete_urb(hcd, urb, status);
+ halt_status = DWC_OTG_HC_XFER_URB_COMPLETE;
+ break;
+ }
+ complete_non_periodic_xfer(hcd, hc, regs, qtd,
+ halt_status, must_free);
+ break;
+ case PIPE_BULK:
+ urb_xfer_done = update_urb_state_xfer_comp(hc, regs, urb, qtd,
+ &status);
+ if (urb_xfer_done) {
+ dwc_otg_hcd_complete_urb(hcd, urb, status);
+ halt_status = DWC_OTG_HC_XFER_URB_COMPLETE;
+ } else {
+ halt_status = DWC_OTG_HC_XFER_COMPLETE;
+ }
+
+ save_data_toggle(hc, regs, qtd);
+ complete_non_periodic_xfer(hcd, hc, regs, qtd,
+ halt_status, must_free);
+ break;
+ case PIPE_INTERRUPT:
+ update_urb_state_xfer_comp(hc, regs, urb, qtd, &status);
+ /*
+ * Interrupt URB is done on the first transfer complete
+ * interrupt.
+ */
+ dwc_otg_hcd_complete_urb(hcd, urb, status);
+ save_data_toggle(hc, regs, qtd);
+ complete_periodic_xfer(hcd, hc, regs, qtd,
+ DWC_OTG_HC_XFER_URB_COMPLETE, must_free);
+ break;
+ case PIPE_ISOCHRONOUS:
+ if (qtd->isoc_split_pos == DWC_HCSPLIT_XACTPOS_ALL) {
+ halt_status = update_isoc_urb_state(hcd, hc, regs, qtd,
+ DWC_OTG_HC_XFER_COMPLETE);
+ }
+ complete_periodic_xfer(hcd, hc, regs, qtd,
+ halt_status, must_free);
+ break;
+ }
+
+ /* disable xfercompl */
+ hcintmsk = DWC_HCINTMSK_TXFER_CMPL_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Handles a host channel STALL interrupt. This handler may be called in
+ * either DMA mode or Slave mode.
+ */
+static int handle_hc_stall_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd, int *must_free)
+{
+ struct urb *urb = qtd->urb;
+ int pipe_type = usb_pipetype(urb->pipe);
+ u32 hcintmsk = 0;
+
+ if (pipe_type == PIPE_CONTROL)
+ dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EPIPE);
+
+ if (pipe_type == PIPE_BULK || pipe_type == PIPE_INTERRUPT) {
+ dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EPIPE);
+ /*
+ * USB protocol requires resetting the data toggle for bulk
+ * and interrupt endpoints when a CLEAR_FEATURE(ENDPOINT_HALT)
+ * setup command is issued to the endpoint. Anticipate the
+ * CLEAR_FEATURE command since a STALL has occurred and reset
+ * the data toggle now.
+ */
+ hc->qh->data_toggle = 0;
+ }
+
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_STALL, must_free);
+ /* disable stall */
+ hcintmsk = DWC_HCINTMSK_STALL_RESP_REC_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Updates the state of the URB when a transfer has been stopped due to an
+ * abnormal condition before the transfer completes. Modifies the
+ * actual_length field of the URB to reflect the number of bytes that have
+ * actually been transferred via the host channel.
+ */
+static void update_urb_state_xfer_intr(struct dwc_hc *hc,
+ u32 regs, struct urb *urb,
+ struct dwc_qtd *qtd,
+ enum dwc_halt_status sts)
+{
+ u32 xfr_len = get_actual_xfer_length(hc, regs, qtd, sts, NULL);
+ urb->actual_length += xfr_len;
+}
+
+/**
+ * Handles a host channel NAK interrupt. This handler may be called in either
+ * DMA mode or Slave mode.
+ */
+static int handle_hc_nak_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd, int *must_free)
+{
+ u32 hcintmsk = 0;
+
+ /*
+ * Handle NAK for IN/OUT SSPLIT/CSPLIT transfers, bulk, control, and
+ * interrupt. Re-start the SSPLIT transfer.
+ */
+ if (hc->do_split) {
+ if (hc->complete_split)
+ qtd->error_count = 0;
+
+ qtd->complete_split = 0;
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK, must_free);
+ goto handle_nak_done;
+ }
+ switch (usb_pipetype(qtd->urb->pipe)) {
+ case PIPE_CONTROL:
+ case PIPE_BULK:
+ if (hcd->core_if->dma_enable && hc->ep_is_in) {
+ /*
+ * NAK interrupts are enabled on bulk/control IN
+ * transfers in DMA mode for the sole purpose of
+ * resetting the error count after a transaction error
+ * occurs. The core will continue transferring data.
+ */
+ qtd->error_count = 0;
+ goto handle_nak_done;
+ }
+
+ /*
+ * NAK interrupts normally occur during OUT transfers in DMA
+ * or Slave mode. For IN transfers, more requests will be
+ * queued as request queue space is available.
+ */
+ qtd->error_count = 0;
+ if (!hc->qh->ping_state) {
+ update_urb_state_xfer_intr(hc, regs, qtd->urb, qtd,
+ DWC_OTG_HC_XFER_NAK);
+
+ save_data_toggle(hc, regs, qtd);
+ if (qtd->urb->dev->speed == USB_SPEED_HIGH)
+ hc->qh->ping_state = 1;
+ }
+
+ /*
+ * Halt the channel so the transfer can be re-started from
+ * the appropriate point or the PING protocol will
+ * start/continue.
+ */
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK, must_free);
+ break;
+ case PIPE_INTERRUPT:
+ qtd->error_count = 0;
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NAK, must_free);
+ break;
+ case PIPE_ISOCHRONOUS:
+ /* Should never get called for isochronous transfers. */
+ BUG();
+ break;
+ }
+
+handle_nak_done:
+ /* disable nak */
+ hcintmsk = DWC_HCINTMSK_NAK_RESP_REC_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Helper function for handle_hc_ack_intr(). Sets the split values for an ACK
+ * on SSPLIT for ISOC OUT.
+ */
+static void set_isoc_out_vals(struct dwc_hc *hc, struct dwc_qtd *qtd)
+{
+ struct usb_iso_packet_descriptor *frame_desc;
+
+ switch (hc->xact_pos) {
+ case DWC_HCSPLIT_XACTPOS_ALL:
+ break;
+ case DWC_HCSPLIT_XACTPOS_END:
+ qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_ALL;
+ qtd->isoc_split_offset = 0;
+ break;
+ case DWC_HCSPLIT_XACTPOS_BEGIN:
+ case DWC_HCSPLIT_XACTPOS_MID:
+ /*
+ * For BEGIN or MID, calculate the length for the next
+ * microframe to determine the correct SSPLIT token, either MID
+ * or END.
+ */
+ frame_desc = &qtd->urb->iso_frame_desc[qtd->isoc_frame_index];
+ qtd->isoc_split_offset += 188;
+
+ if ((frame_desc->length - qtd->isoc_split_offset) <= 188)
+ qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_END;
+ else
+ qtd->isoc_split_pos = DWC_HCSPLIT_XACTPOS_MID;
+
+ break;
+ }
+}
+
+/**
+ * Handles a host channel ACK interrupt. This interrupt is enabled when
+ * performing the PING protocol in Slave mode, when errors occur during
+ * either Slave mode or DMA mode, and during Start Split transactions.
+ */
+static int handle_hc_ack_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd, int *must_free)
+{
+ u32 hcintmsk = 0;
+
+ if (hc->do_split) {
+ /* Handle ACK on SSPLIT. ACK should not occur in CSPLIT. */
+ if (!hc->ep_is_in && hc->data_pid_start != DWC_OTG_HC_PID_SETUP)
+ qtd->ssplit_out_xfer_count = hc->xfer_len;
+
+ /* Don't need complete for isochronous out transfers. */
+ if (!(hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in))
+ qtd->complete_split = 1;
+
+ if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in)
+ set_isoc_out_vals(hc, qtd);
+ else
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK,
+ must_free);
+ } else {
+ qtd->error_count = 0;
+ if (hc->qh->ping_state) {
+ hc->qh->ping_state = 0;
+
+ /*
+ * Halt the channel so the transfer can be re-started
+ * from the appropriate point. This only happens in
+ * Slave mode. In DMA mode, the ping_state is cleared
+ * when the transfer is started because the core
+ * automatically executes the PING, then the transfer.
+ */
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_ACK,
+ must_free);
+ }
+ }
+
+ /*
+ * If the ACK occurred when _not_ in the PING state, let the channel
+ * continue transferring data after clearing the error count.
+ */
+ /* disable ack */
+ hcintmsk = DWC_HCINTMSK_ACK_RESP_REC_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Handles a host channel NYET interrupt. This interrupt should only occur on
+ * Bulk and Control OUT endpoints and for complete split transactions. If a
+ * NYET occurs at the same time as a Transfer Complete interrupt, it is
+ * handled in the xfercomp interrupt handler, not here. This handler may be
+ * called in either DMA mode or Slave mode.
+ */
+static int handle_hc_nyet_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd, int *must_free)
+{
+ u32 hcintmsk = 0;
+ u32 hcint_clear = 0;
+
+ /*
+ * NYET on CSPLIT
+ * re-do the CSPLIT immediately on non-periodic
+ */
+ if (hc->do_split && hc->complete_split) {
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ int frnum =
+ dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd
+ (hcd));
+ if (dwc_full_frame_num(frnum) !=
+ dwc_full_frame_num(hc->qh->sched_frame)) {
+ qtd->complete_split = 0;
+ halt_channel(hcd, hc, qtd,
+ DWC_OTG_HC_XFER_XACT_ERR,
+ must_free);
+ goto handle_nyet_done;
+ }
+ }
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET, must_free);
+ goto handle_nyet_done;
+ }
+ hc->qh->ping_state = 1;
+ qtd->error_count = 0;
+ update_urb_state_xfer_intr(hc, regs, qtd->urb, qtd,
+ DWC_OTG_HC_XFER_NYET);
+ save_data_toggle(hc, regs, qtd);
+ /*
+ * Halt the channel and re-start the transfer so the PING
+ * protocol will start.
+ */
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_NYET, must_free);
+
+handle_nyet_done:
+ /* disable nyet */
+ hcintmsk = DWC_HCINTMSK_NYET_RESP_REC_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+ /* clear nyet */
+ hcint_clear = DWC_HCINT_NYET_RESP_REC_RW(hcint_clear, 1);
+ dwc_reg_write(regs, DWC_HCINT, hcint_clear);
+ return 1;
+}
+
+/**
+ * Handles a host channel babble interrupt. This handler may be called in
+ * either DMA mode or Slave mode.
+ */
+static int handle_hc_babble_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd, int *must_free)
+{
+ u32 hcintmsk = 0;
+
+ if (hc->ep_type != DWC_OTG_EP_TYPE_ISOC) {
+ dwc_otg_hcd_complete_urb(hcd, qtd->urb, -EOVERFLOW);
+ halt_channel(hcd, hc, qtd, DWC_OTG_HC_XFER_BABBLE_ERR,
+ must_free);
+ } else {
+ enum dwc_halt_status halt_status;
+ halt_status = update_isoc_urb_state(hcd, hc, regs, qtd,
+ DWC_OTG_HC_XFER_BABBLE_ERR);
+ halt_channel(hcd, hc, qtd, halt_status, must_free);
+ }
+ /* disable bblerr */
+ hcintmsk = DWC_HCINTMSK_BBL_ERR_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+ return 1;
+}
+
+/**
+ * Handles a host channel AHB error interrupt. This handler is only called in
+ * DMA mode.
+ */
+static int handle_hc_ahberr_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd)
+{
+ u32 hcchar;
+ u32 hcsplt;
+ u32 hctsiz = 0;
+ u32 hcdma;
+ struct urb *urb = qtd->urb;
+ u32 hcintmsk = 0;
+
+ hcchar = dwc_reg_read(regs, DWC_HCCHAR);
+ hcsplt = dwc_reg_read(regs, DWC_HCSPLT);
+ hctsiz = dwc_reg_read(regs, DWC_HCTSIZ);
+ hcdma = dwc_reg_read(regs, DWC_HCDMA);
+
+ pr_err("AHB ERROR, Channel %d\n", hc->hc_num);
+ pr_err(" hcchar 0x%08x, hcsplt 0x%08x\n", hcchar, hcsplt);
+ pr_err(" hctsiz 0x%08x, hcdma 0x%08x\n", hctsiz, hcdma);
+
+ pr_err(" Device address: %d\n", usb_pipedevice(urb->pipe));
+ pr_err(" Endpoint: %d, %s\n", usb_pipeendpoint(urb->pipe),
+ (usb_pipein(urb->pipe) ? "IN" : "OUT"));
+
+ pr_err(" Endpoint type: %s\n", pipetype_str(urb->pipe));
+ pr_err(" Speed: %s\n", dev_speed_str(urb->dev->speed));
+ pr_err(" Max packet size: %d\n",
+ usb_maxpacket(urb->dev, urb->pipe, usb_pipeout(urb->pipe)));
+ pr_err(" Data buffer length: %d\n", urb->transfer_buffer_length);
+ pr_err(" Transfer buffer: %p, Transfer DMA: %p\n",
+ urb->transfer_buffer, (void *)(u32) urb->transfer_dma);
+ pr_err(" Setup buffer: %p, Setup DMA: %p\n",
+ urb->setup_packet, (void *)(u32) urb->setup_dma);
+ pr_err(" Interval: %d\n", urb->interval);
+
+ dwc_otg_hcd_complete_urb(hcd, urb, -EIO);
+
+ /*
+ * Force a channel halt. Don't call halt_channel because that won't
+ * write to the HCCHARn register in DMA mode to force the halt.
+ */
+ dwc_otg_hc_halt(hcd->core_if, hc, DWC_OTG_HC_XFER_AHB_ERR);
+ /* disable ahberr */
+ hcintmsk = DWC_HCINTMSK_AHB_ERR_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Handles a host channel transaction error interrupt. This handler may be
+ * called in either DMA mode or Slave mode.
+ */
+static int handle_hc_xacterr_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd, int *must_free)
+{
+ enum dwc_halt_status status = DWC_OTG_HC_XFER_XACT_ERR;
+ u32 hcintmsk = 0;
+
+ switch (usb_pipetype(qtd->urb->pipe)) {
+ case PIPE_CONTROL:
+ case PIPE_BULK:
+ qtd->error_count++;
+ if (!hc->qh->ping_state) {
+ update_urb_state_xfer_intr(hc, regs, qtd->urb, qtd,
+ status);
+ save_data_toggle(hc, regs, qtd);
+
+ if (!hc->ep_is_in && qtd->urb->dev->speed ==
+ USB_SPEED_HIGH)
+ hc->qh->ping_state = 1;
+ }
+ /*
+ * Halt the channel so the transfer can be re-started from
+ * the appropriate point or the PING protocol will start.
+ */
+ halt_channel(hcd, hc, qtd, status, must_free);
+ break;
+ case PIPE_INTERRUPT:
+ qtd->error_count++;
+ if (hc->do_split && hc->complete_split)
+ qtd->complete_split = 0;
+
+ halt_channel(hcd, hc, qtd, status, must_free);
+ break;
+ case PIPE_ISOCHRONOUS:
+ status = update_isoc_urb_state(hcd, hc, regs, qtd, status);
+ halt_channel(hcd, hc, qtd, status, must_free);
+ break;
+ }
+ /* Disable xacterr */
+ hcintmsk = DWC_HCINTMSK_TRANS_ERR_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Handles a host channel frame overrun interrupt. This handler may be called
+ * in either DMA mode or Slave mode.
+ */
+static int handle_hc_frmovrun_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd,
+ int *must_free)
+{
+ enum dwc_halt_status status = DWC_OTG_HC_XFER_FRAME_OVERRUN;
+ u32 hcintmsk = 0;
+
+ switch (usb_pipetype(qtd->urb->pipe)) {
+ case PIPE_CONTROL:
+ case PIPE_BULK:
+ break;
+ case PIPE_INTERRUPT:
+ halt_channel(hcd, hc, qtd, status, must_free);
+ break;
+ case PIPE_ISOCHRONOUS:
+ status = update_isoc_urb_state(hcd, hc, regs, qtd, status);
+ halt_channel(hcd, hc, qtd, status, must_free);
+ break;
+ }
+ /* Disable frmovrun */
+ hcintmsk = DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Handles a host channel data toggle error interrupt. This handler may be
+ * called in either DMA mode or Slave mode.
+ */
+static int handle_hc_datatglerr_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u32 regs, struct dwc_qtd *qtd)
+{
+ u32 hcintmsk = 0;
+
+ if (hc->ep_is_in)
+ qtd->error_count = 0;
+ else
+ pr_err("Data Toggle Error on OUT transfer, channel "
+ "%d\n", hc->hc_num);
+
+ /* disable datatglerr */
+ hcintmsk = DWC_HCINTMSK_DATA_TOG_ERR_RW(hcintmsk, 1);
+ dwc_reg_modify(regs, DWC_HCINTMSK, hcintmsk, 0);
+
+ return 1;
+}
+
+/**
+ * Handles a host Channel Halted interrupt in DMA mode. This handler
+ * determines the reason the channel halted and proceeds accordingly.
+ */
+static void handle_hc_chhltd_intr_dma(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ ulong regs, struct dwc_qtd *qtd,
+ int *must_free)
+{
+ u32 hcint;
+ u32 hcintmsk = 0;
+
+ if (hc->halt_status == DWC_OTG_HC_XFER_URB_DEQUEUE ||
+ hc->halt_status == DWC_OTG_HC_XFER_AHB_ERR) {
+ /*
+ * Just release the channel. A dequeue can happen on a
+ * transfer timeout. In the case of an AHB Error, the channel
+ * was forced to halt because there's no way to gracefully
+ * recover.
+ */
+ release_channel(hcd, hc, qtd, hc->halt_status, must_free);
+ return;
+ }
+
+ /* Read the HCINTn register to determine the cause for the halt. */
+ hcint = dwc_reg_read(regs, DWC_HCINT);
+ hcintmsk = dwc_reg_read(regs, DWC_HCINTMSK);
+ if (DWC_HCINT_TXFER_CMPL_RD(hcint)) {
+ /*
+ * This is here because of a possible hardware bug. Spec
+ * says that on SPLIT-ISOC OUT transfers in DMA mode that a HALT
+ * interrupt w/ACK bit set should occur, but I only see the
+ * XFERCOMP bit, even with it masked out. This is a workaround
+ * for that behavior. Should fix this when hardware is fixed.
+ */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC && !hc->ep_is_in)
+ handle_hc_ack_intr(hcd, hc, regs, qtd, must_free);
+
+ handle_hc_xfercomp_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_STALL_RESP_REC_RD(hcint)) {
+ handle_hc_stall_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_TRANS_ERR_RD(hcint)) {
+ /*
+ * Must handle xacterr before nak or ack. Could get a xacterr
+ * at the same time as either of these on a BULK/CONTROL OUT
+ * that started with a PING. The xacterr takes precedence.
+ */
+ handle_hc_xacterr_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_NYET_RESP_REC_RD(hcint)) {
+ /*
+ * Must handle nyet before nak or ack. Could get a nyet at the
+ * same time as either of those on a BULK/CONTROL OUT that
+ * started with a PING. The nyet takes precedence.
+ */
+ handle_hc_nyet_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_BBL_ERR_RD(hcint)) {
+ handle_hc_babble_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_FRAME_OVERN_ERR_RD(hcint)) {
+ handle_hc_frmovrun_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_DATA_TOG_ERR_RD(hcint)) {
+ handle_hc_datatglerr_intr(hcd, hc, regs, qtd);
+ hc->qh->data_toggle = 0;
+ halt_channel(hcd, hc, qtd, hc->halt_status, must_free);
+ } else if (DWC_HCINT_NAK_RESP_REC_RD(hcint) &&
+ !DWC_HCINTMSK_NAK_RESP_REC_RD(hcintmsk)) {
+ /*
+ * If nak is not masked, it's because a non-split IN transfer
+ * is in an error state. In that case, the nak is handled by
+ * the nak interrupt handler, not here. Handle nak here for
+ * BULK/CONTROL OUT transfers, which halt on a NAK to allow
+ * rewinding the buffer pointer.
+ */
+ handle_hc_nak_intr(hcd, hc, regs, qtd, must_free);
+ } else if (DWC_HCINT_ACK_RESP_REC_RD(hcint) &&
+ !DWC_HCINTMSK_ACK_RESP_REC_RD(hcintmsk)) {
+ /*
+ * If ack is not masked, it's because a non-split IN transfer
+ * is in an error state. In that case, the ack is handled by
+ * the ack interrupt handler, not here. Handle ack here for
+ * split transfers. Start splits halt on ACK.
+ */
+ handle_hc_ack_intr(hcd, hc, regs, qtd, must_free);
+ } else {
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ /*
+ * A periodic transfer halted with no other channel
+ * interrupts set. Assume it was halted by the core
+ * because it could not be completed in its scheduled
+ * (micro)frame.
+ */
+ halt_channel(hcd, hc, qtd,
+ DWC_OTG_HC_XFER_PERIODIC_INCOMPLETE,
+ must_free);
+ } else {
+ pr_err("%s: Channel %d, DMA Mode -- ChHltd "
+ "set, but reason for halting is unknown, "
+ "hcint 0x%08x, intsts 0x%08x\n",
+ __func__, hc->hc_num, hcint,
+ dwc_reg_read(gintsts_reg(hcd), 0));
+ }
+ }
+}
+
+/**
+ * Handles a host channel Channel Halted interrupt.
+ *
+ * In slave mode, this handler is called only when the driver specifically
+ * requests a halt. This occurs during handling other host channel interrupts
+ * (e.g. nak, xacterr, stall, nyet, etc.).
+ *
+ * In DMA mode, this is the interrupt that occurs when the core has finished
+ * processing a transfer on a channel. Other host channel interrupts (except
+ * ahberr) are disabled in DMA mode.
+ */
+static int handle_hc_chhltd_intr(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ ulong regs, struct dwc_qtd *qtd, int *must_free)
+{
+ if (hcd->core_if->dma_enable)
+ handle_hc_chhltd_intr_dma(hcd, hc, regs, qtd, must_free);
+ else
+ release_channel(hcd, hc, qtd, hc->halt_status, must_free);
+
+ return 1;
+}
+
+/* Handles interrupt for a specific Host Channel */
+static int dwc_otg_hcd_handle_hc_n_intr(struct dwc_hcd *hcd, u32 num)
+{
+ int must_free = 0;
+ int retval = 0;
+ u32 hcint;
+ u32 hcintmsk = 0;
+ struct dwc_hc *hc;
+ ulong hc_regs;
+ struct dwc_qtd *qtd;
+
+ hc = hcd->hc_ptr_array[num];
+ hc_regs = hcd->core_if->host_if->hc_regs[num];
+ qtd = list_entry(hc->qh->qtd_list.next, struct dwc_qtd, qtd_list_entry);
+
+ hcint = dwc_reg_read(hc_regs, DWC_HCINT);
+ hcintmsk = dwc_reg_read(hc_regs, DWC_HCINTMSK);
+
+ hcint = hcint & hcintmsk;
+ if (!hcd->core_if->dma_enable && DWC_HCINT_CHAN_HALTED_RD(hcint)
+ && hcint != 0x2)
+ hcint = DWC_HCINT_CHAN_HALTED_RW(hcint, 0);
+
+ if (DWC_HCINT_TXFER_CMPL_RD(hcint)) {
+ retval |= handle_hc_xfercomp_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ /*
+ * If NYET occurred at same time as Xfer Complete, the NYET is
+ * handled by the Xfer Complete interrupt handler. Don't want
+ * to call the NYET interrupt handler in this case.
+ */
+ hcint = DWC_HCINT_NYET_RESP_REC_RW(hcint, 0);
+ }
+
+ if (DWC_HCINT_CHAN_HALTED_RD(hcint))
+ retval |= handle_hc_chhltd_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ if (DWC_HCINT_AHB_ERR_RD(hcint))
+ retval |= handle_hc_ahberr_intr(hcd, hc, hc_regs, qtd);
+ if (DWC_HCINT_STALL_RESP_REC_RD(hcint))
+ retval |= handle_hc_stall_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ if (DWC_HCINT_NAK_RESP_REC_RD(hcint))
+ retval |= handle_hc_nak_intr(hcd, hc, hc_regs, qtd, &must_free);
+ if (DWC_HCINT_ACK_RESP_REC_RD(hcint))
+ retval |= handle_hc_ack_intr(hcd, hc, hc_regs, qtd, &must_free);
+ if (DWC_HCINT_NYET_RESP_REC_RD(hcint))
+ retval |= handle_hc_nyet_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ if (DWC_HCINT_TRANS_ERR_RD(hcint))
+ retval |= handle_hc_xacterr_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ if (DWC_HCINT_BBL_ERR_RD(hcint))
+ retval |= handle_hc_babble_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ if (DWC_HCINT_FRAME_OVERN_ERR_RD(hcint))
+ retval |= handle_hc_frmovrun_intr(hcd, hc, hc_regs,
+ qtd, &must_free);
+ if (DWC_HCINT_DATA_TOG_ERR_RD(hcint))
+ retval |= handle_hc_datatglerr_intr(hcd, hc, hc_regs, qtd);
+
+ if (must_free)
+ /* Free the qtd here now that we are done using it. */
+ dwc_otg_hcd_qtd_free(qtd);
+ return retval;
+}
+
+/**
+ * This function returns the Host All Channel Interrupt register
+ */
+static inline u32 dwc_otg_read_host_all_channels_intr(struct core_if
+ *core_if)
+{
+ return dwc_reg_read(core_if->host_if->host_global_regs, DWC_HAINT);
+}
+
+/**
+ * This interrupt indicates that one or more host channels has a pending
+ * interrupt. There are multiple conditions that can cause each host channel
+ * interrupt. This function determines which conditions have occurred for each
+ * host channel interrupt and handles them appropriately.
+ */
+static int dwc_otg_hcd_handle_hc_intr(struct dwc_hcd *hcd)
+{
+ u32 i;
+ int retval = 0;
+ u32 haint;
+
+ /*
+ * Clear appropriate bits in HCINTn to clear the interrupt bit in
+ * GINTSTS
+ */
+ haint = dwc_otg_read_host_all_channels_intr(hcd->core_if);
+ for (i = 0; i < hcd->core_if->core_params->host_channels; i++)
+ if (DWC_HAINT_RD(haint) & (1 << i))
+ retval |= dwc_otg_hcd_handle_hc_n_intr(hcd, i);
+
+ return retval;
+}
+
+/* This function handles interrupts for the HCD.*/
+int dwc_otg_hcd_handle_intr(struct dwc_hcd *hcd)
+{
+ int ret = 0;
+ struct core_if *core_if = hcd->core_if;
+ u32 gintsts;
+
+ /* Check if HOST Mode */
+ if (dwc_otg_is_host_mode(core_if)) {
+ spin_lock(&hcd->lock);
+ gintsts = dwc_otg_read_core_intr(core_if);
+ if (!gintsts) {
+ spin_unlock(&hcd->lock);
+ return IRQ_NONE;
+ }
+
+ if (gintsts & DWC_INTMSK_STRT_OF_FRM)
+ ret |= dwc_otg_hcd_handle_sof_intr(hcd);
+ if (gintsts & DWC_INTMSK_RXFIFO_NOT_EMPT)
+ ret |= dwc_otg_hcd_handle_rx_status_q_level_intr(hcd);
+ if (gintsts & DWC_INTMSK_NP_TXFIFO_EMPT)
+ ret |= dwc_otg_hcd_handle_np_tx_fifo_empty_intr(hcd);
+ if (gintsts & DWC_INTMSK_HST_PORT)
+ ret |= dwc_otg_hcd_handle_port_intr(hcd);
+ if (gintsts & DWC_INTMSK_HST_CHAN)
+ ret |= dwc_otg_hcd_handle_hc_intr(hcd);
+ if (gintsts & DWC_INTMSK_P_TXFIFO_EMPTY)
+ ret |= dwc_otg_hcd_handle_perio_tx_fifo_empty_intr(hcd);
+
+ spin_unlock(&hcd->lock);
+ }
+ return ret;
+}
--
1.7.0.4
^ permalink raw reply related
* [PATCH v16 04/10]USB/ppc4xx: Add Synopsys DWC OTG HCD function
From: Rupjyoti Sarmah @ 2012-05-03 12:32 UTC (permalink / raw)
To: linuxppc-dev, linux-kernel; +Cc: rsarmah
Implements DWC OTG USB Host Controller Driver (HCD) and interface to
USB Host controller Driver framework.
Signed-off-by: Rupjyoti Sarmah <rsarmah@apm.com>
Signed-off-by: Tirumala R Marri <tmarri@apm.com>
Signed-off-by: Fushen Chen <fchen@apm.com>
Signed-off-by: Mark Miesfeld <mmiesfeld@apm.com>
---
drivers/usb/dwc/hcd.c | 2435 +++++++++++++++++++++++++++++++++++++++++++++++++
drivers/usb/dwc/hcd.h | 416 +++++++++
2 files changed, 2851 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/dwc/hcd.c
create mode 100644 drivers/usb/dwc/hcd.h
diff --git a/drivers/usb/dwc/hcd.c b/drivers/usb/dwc/hcd.c
new file mode 100644
index 0000000..b237957
--- /dev/null
+++ b/drivers/usb/dwc/hcd.c
@@ -0,0 +1,2435 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ * Modified by Chuck Meade <chuck@theptrgroup.com>
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/*
+ * This file contains the implementation of the HCD. In Linux, the HCD
+ * implements the hc_driver API.
+ */
+
+#include <asm/unaligned.h>
+#include <linux/dma-mapping.h>
+
+#include "hcd.h"
+
+static const char dwc_otg_hcd_name[] = "dwc_otg_hcd";
+
+/**
+ * Clears the transfer state for a host channel. This function is normally
+ * called after a transfer is done and the host channel is being released. It
+ * clears the channel interrupt enables and any unhandled channel interrupt
+ * conditions.
+ */
+void dwc_otg_hc_cleanup(struct core_if *core_if, struct dwc_hc *hc)
+{
+ ulong regs;
+
+ hc->xfer_started = 0;
+ regs = core_if->host_if->hc_regs[hc->hc_num];
+ dwc_reg_write(regs, DWC_HCINTMSK, 0);
+ dwc_reg_write(regs, DWC_HCINT, 0xFFFFFFFF);
+}
+
+/**
+ * This function enables the Host mode interrupts.
+ */
+static void dwc_otg_enable_host_interrupts(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ u32 intr_mask = 0;
+
+ /* Disable all interrupts. */
+ dwc_reg_write(global_regs, DWC_GINTMSK, 0);
+
+ /* Clear any pending interrupts. */
+ dwc_reg_write(global_regs, DWC_GINTSTS, 0xFFFFFFFF);
+
+ /* Enable the common interrupts */
+ dwc_otg_enable_common_interrupts(core_if);
+
+ /*
+ * Enable host mode interrupts without disturbing common
+ * interrupts.
+ */
+ intr_mask |= DWC_INTMSK_STRT_OF_FRM;
+ intr_mask |= DWC_INTMSK_HST_PORT;
+ intr_mask |= DWC_INTMSK_HST_CHAN;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, intr_mask);
+}
+
+/**
+ * This function initializes the DWC_otg controller registers for
+ * host mode.
+ *
+ * This function flushes the Tx and Rx FIFOs and it flushes any entries in the
+ * request queues. Host channels are reset to ensure that they are ready for
+ * performing transfers.
+ */
+static void dwc_otg_core_host_init(struct core_if *core_if)
+{
+ ulong global_regs = core_if->core_global_regs;
+ struct dwc_host_if *host_if = core_if->host_if;
+ struct core_params *params = core_if->core_params;
+ u32 hprt0 = 0;
+ u32 nptxfifosize = 0;
+ u32 ptxfifosize = 0;
+ u32 i;
+ u32 hcchar;
+ ulong hcfg;
+ ulong hc_regs;
+ int num_channels;
+ u32 gotgctl = 0;
+
+ /* Restart the Phy Clock */
+ dwc_reg_write(core_if->pcgcctl, 0, 0);
+
+ /* Initialize Host Configuration Register */
+ init_fslspclksel(core_if);
+ if (core_if->core_params->speed == DWC_SPEED_PARAM_FULL) {
+ hcfg = dwc_reg_read(host_if->host_global_regs, DWC_HCFG);
+ hcfg = DWC_HCFG_FSLSUPP_RW(hcfg, 1);
+ dwc_reg_write(host_if->host_global_regs, DWC_HCFG, hcfg);
+ }
+
+ /* Configure data FIFO sizes */
+ if (DWC_HWCFG2_DYN_FIFO_RD(core_if->hwcfg2)
+ && params->enable_dynamic_fifo) {
+ /* Rx FIFO */
+ dwc_reg_write(global_regs, DWC_GRXFSIZ,
+ params->host_rx_fifo_size);
+
+ /* Non-periodic Tx FIFO */
+ nptxfifosize = DWC_TX_FIFO_DEPTH_WR(nptxfifosize,
+ params->
+ host_nperio_tx_fifo_size);
+ nptxfifosize =
+ DWC_TX_FIFO_START_ADDR_WR(nptxfifosize,
+ params->host_rx_fifo_size);
+ dwc_reg_write(global_regs, DWC_GNPTXFSIZ, nptxfifosize);
+
+ /* Periodic Tx FIFO */
+ ptxfifosize = DWC_TX_FIFO_DEPTH_WR(ptxfifosize,
+ params->
+ host_perio_tx_fifo_size);
+ ptxfifosize =
+ DWC_TX_FIFO_START_ADDR_WR(ptxfifosize,
+ (DWC_TX_FIFO_START_ADDR_RD
+ (nptxfifosize) +
+ DWC_TX_FIFO_DEPTH_RD
+ (nptxfifosize)));
+ dwc_reg_write(global_regs, DWC_HPTXFSIZ, ptxfifosize);
+ }
+
+ /* Clear Host Set HNP Enable in the OTG Control Register */
+ gotgctl |= DWC_GCTL_HOST_HNP_ENA;
+ dwc_reg_modify(global_regs, DWC_GOTGCTL, gotgctl, 0);
+
+ /* Make sure the FIFOs are flushed. */
+ dwc_otg_flush_tx_fifo(core_if, DWC_GRSTCTL_TXFNUM_ALL);
+ dwc_otg_flush_rx_fifo(core_if);
+
+ /* Flush out any leftover queued requests. */
+ num_channels = core_if->core_params->host_channels;
+ for (i = 0; i < num_channels; i++) {
+ hc_regs = core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+ }
+
+ /* Halt all channels to put them into a known state. */
+ for (i = 0; i < num_channels; i++) {
+ int count = 0;
+
+ hc_regs = core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ do {
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ if (++count > 200) {
+ pr_err("%s: Unable to clear halt on "
+ "channel %d\n", __func__, i);
+ break;
+ }
+ udelay(100);
+ } while (DWC_HCCHAR_ENA_RD(hcchar));
+ }
+
+ /* Turn on the vbus power. */
+ pr_info("Init: Port Power? op_state=%s\n",
+ otg_state_string(core_if->xceiv->state));
+
+ if (core_if->xceiv->state == OTG_STATE_A_HOST) {
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ pr_info("Init: Power Port (%d)\n", DWC_HPRT0_PRT_PWR_RD(hprt0));
+ if (DWC_HPRT0_PRT_PWR_RD(hprt0) == 0) {
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1);
+ dwc_reg_write(host_if->hprt0, 0, hprt0);
+ }
+ }
+ dwc_otg_enable_host_interrupts(core_if);
+}
+
+/**
+ * Initializes dynamic portions of the DWC_otg HCD state.
+ */
+static void hcd_reinit(struct dwc_hcd *hcd)
+{
+ struct list_head *item;
+ int num_channels;
+ u32 i;
+ struct dwc_hc *channel;
+
+ hcd->non_periodic_qh_ptr = &hcd->non_periodic_sched_active;
+ hcd->available_host_channels = hcd->core_if->core_params->host_channels;
+
+ /*
+ * Put all channels in the free channel list and clean up channel
+ * states.
+ */
+ item = hcd->free_hc_list.next;
+ while (item != &hcd->free_hc_list) {
+ list_del(item);
+ item = hcd->free_hc_list.next;
+ }
+
+ num_channels = hcd->core_if->core_params->host_channels;
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ list_add_tail(&channel->hc_list_entry, &hcd->free_hc_list);
+ dwc_otg_hc_cleanup(hcd->core_if, channel);
+ }
+
+ /* Initialize the DWC core for host mode operation. */
+ dwc_otg_core_host_init(hcd->core_if);
+}
+
+/* Gets the dwc_hcd from a struct usb_hcd */
+static inline struct dwc_hcd *hcd_to_dwc_otg_hcd(struct usb_hcd *hcd)
+{
+ return (struct dwc_hcd *)hcd->hcd_priv;
+}
+
+/**
+ * Initializes the DWC_otg controller and its root hub and prepares it for host
+ * mode operation. Activates the root port. Returns 0 on success and a negative
+ * error code on failure.
+*/
+
+static int dwc_otg_hcd_start(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ struct usb_bus *bus = hcd_to_bus(hcd);
+
+ hcd->state = HC_STATE_RUNNING;
+
+ /* Inform the HUB driver to resume. */
+ if (bus->root_hub)
+ usb_hcd_resume_root_hub(hcd);
+
+ hcd_reinit(dwc_hcd);
+ return 0;
+}
+
+/**
+ * Work queue function for starting the HCD when A-Cable is connected.
+ * The dwc_otg_hcd_start() must be called in a process context.
+ */
+
+static void hcd_start_func(struct work_struct *work)
+{
+ struct dwc_hcd *priv = container_of(work, struct dwc_hcd, start_work);
+ struct usb_hcd *usb_hcd = (struct usb_hcd *)priv->_p;
+
+ if (usb_hcd)
+ dwc_otg_hcd_start(usb_hcd);
+}
+
+/**
+ * HCD Callback function for starting the HCD when A-Cable is
+ * connected.
+ */
+static int dwc_otg_hcd_start_cb(void *_p)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(_p);
+ struct core_if *core_if = dwc_hcd->core_if;
+ u32 hprt0;
+
+ if (core_if->xceiv->state == OTG_STATE_B_HOST) {
+ /*
+ * Reset the port. During a HNP mode switch the reset
+ * needs to occur within 1ms and have a duration of at
+ * least 50ms.
+ */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ ((struct usb_hcd *)_p)->self.is_b_host = 1;
+ } else {
+ ((struct usb_hcd *)_p)->self.is_b_host = 0;
+ }
+
+ /* Need to start the HCD in a non-interrupt context. */
+ dwc_hcd->_p = _p;
+ schedule_work(&dwc_hcd->start_work);
+ return 1;
+}
+
+/**
+ * This function disables the Host Mode interrupts.
+ */
+static void dwc_otg_disable_host_interrupts(struct core_if *core_if)
+{
+ u32 global_regs = core_if->core_global_regs;
+ u32 intr_mask = 0;
+
+ /*
+ * Disable host mode interrupts without disturbing common
+ * interrupts.
+ */
+ intr_mask |= DWC_INTMSK_STRT_OF_FRM;
+ intr_mask |= DWC_INTMSK_HST_PORT;
+ intr_mask |= DWC_INTMSK_HST_CHAN;
+ intr_mask |= DWC_INTMSK_P_TXFIFO_EMPTY;
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, intr_mask, 0);
+}
+
+/**
+ * Halts the DWC_otg host mode operations in a clean manner. USB transfers are
+ * stopped.
+ */
+static void dwc_otg_hcd_stop(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ u32 hprt0 = 0;
+
+ /* Turn off all host-specific interrupts. */
+ spin_lock(&dwc_hcd->lock);
+ dwc_otg_disable_host_interrupts(dwc_hcd->core_if);
+ spin_unlock(&dwc_hcd->lock);
+
+ /*
+ * The root hub should be disconnected before this function is called.
+ * The disconnect will clear the QTD lists (via ..._hcd_urb_dequeue)
+ * and the QH lists (via ..._hcd_endpoint_disable).
+ */
+
+ /* Turn off the vbus power */
+ pr_info("PortPower off\n");
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(dwc_hcd->core_if->host_if->hprt0, 0, hprt0);
+}
+
+/**
+ * HCD Callback function for stopping the HCD.
+ */
+static int dwc_otg_hcd_stop_cb(void *_p)
+{
+ struct usb_hcd *usb_hcd = (struct usb_hcd *)_p;
+
+ dwc_otg_hcd_stop(usb_hcd);
+ return 1;
+}
+
+static void del_timers(struct dwc_hcd *hcd)
+{
+ del_timer_sync(&hcd->conn_timer);
+}
+
+/**
+ * Processes all the URBs in a single list of QHs. Completes them with
+ * -ETIMEDOUT and frees the QTD.
+ */
+static void kill_urbs_in_qh_list(struct dwc_hcd *hcd, struct list_head *qh_list)
+{
+ struct list_head *qh_item, *q;
+
+ qh_item = qh_list->next;
+ list_for_each_safe(qh_item, q, qh_list) {
+ struct dwc_qh *qh;
+ struct list_head *qtd_item;
+ struct dwc_qtd *qtd;
+
+ qh = list_entry(qh_item, struct dwc_qh, qh_list_entry);
+ qtd_item = qh->qtd_list.next;
+ qtd = list_entry(qtd_item, struct dwc_qtd, qtd_list_entry);
+ if (qtd->urb != NULL) {
+ spin_lock(&hcd->lock);
+ dwc_otg_hcd_complete_urb(hcd, qtd->urb, -ETIMEDOUT);
+ dwc_otg_hcd_qtd_remove_and_free(qtd);
+ spin_unlock(&hcd->lock);
+ }
+ }
+}
+
+/**
+ * Responds with an error status of ETIMEDOUT to all URBs in the non-periodic
+ * and periodic schedules. The QTD associated with each URB is removed from
+ * the schedule and freed. This function may be called when a disconnect is
+ * detected or when the HCD is being stopped.
+ */
+static void kill_all_urbs(struct dwc_hcd *hcd)
+{
+ kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_deferred);
+ kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_inactive);
+ kill_urbs_in_qh_list(hcd, &hcd->non_periodic_sched_active);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_inactive);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_ready);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_assigned);
+ kill_urbs_in_qh_list(hcd, &hcd->periodic_sched_queued);
+}
+
+/**
+ * HCD Callback function for disconnect of the HCD.
+ */
+static int dwc_otg_hcd_disconnect_cb(void *_p)
+{
+ u32 intr;
+ struct dwc_hcd *hcd = hcd_to_dwc_otg_hcd(_p);
+ struct core_if *core_if = hcd->core_if;
+
+ /*
+ * Shutdown any transfers in process by clearing the Tx FIFO Empty
+ * interrupt mask and status bits and disabling subsequent host
+ * channel interrupts.
+ */
+ intr = 0;
+ intr |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ intr |= DWC_INTMSK_P_TXFIFO_EMPTY;
+ intr |= DWC_INTMSK_HST_CHAN;
+ spin_lock(&hcd->lock);
+ dwc_reg_modify(gintmsk_reg(hcd), 0, intr, 0);
+ dwc_reg_modify(gintsts_reg(hcd), 0, intr, 0);
+ spin_unlock(&hcd->lock);
+
+ del_timers(hcd);
+
+ /*
+ * Turn off the vbus power only if the core has transitioned to device
+ * mode. If still in host mode, need to keep power on to detect a
+ * reconnection.
+ */
+ if (dwc_otg_is_device_mode(core_if)) {
+ if (core_if->xceiv->state != OTG_STATE_A_SUSPEND) {
+ u32 hprt0 = 0;
+
+ pr_info("Disconnect: PortPower off\n");
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ }
+ dwc_otg_disable_host_interrupts(core_if);
+ }
+
+ /* Respond with an error status to all URBs in the schedule. */
+ kill_all_urbs(hcd);
+ if (dwc_otg_is_host_mode(core_if)) {
+ /* Clean up any host channels that were in use. */
+ int num_channels;
+ u32 i;
+ struct dwc_hc *channel;
+ ulong regs;
+ u32 hcchar;
+
+ num_channels = core_if->core_params->host_channels;
+ if (!core_if->dma_enable) {
+ /* Flush out any channel requests in slave mode. */
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ if (list_empty(&channel->hc_list_entry)) {
+ regs =
+ core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(regs, DWC_HCCHAR);
+
+ if (DWC_HCCHAR_ENA_RD(hcchar)) {
+ hcchar =
+ DWC_HCCHAR_ENA_RW(hcchar,
+ 0);
+ hcchar =
+ DWC_HCCHAR_DIS_RW(hcchar,
+ 1);
+ hcchar =
+ DWC_HCCHAR_EPDIR_RW(hcchar,
+ 0);
+ dwc_reg_write(regs, DWC_HCCHAR,
+ hcchar);
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < num_channels; i++) {
+ channel = hcd->hc_ptr_array[i];
+ if (list_empty(&channel->hc_list_entry)) {
+ regs = core_if->host_if->hc_regs[i];
+ hcchar = dwc_reg_read(regs, DWC_HCCHAR);
+
+ if (DWC_HCCHAR_ENA_RD(hcchar)) {
+ /* Halt the channel. */
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ dwc_reg_write(regs, DWC_HCCHAR, hcchar);
+ }
+ dwc_otg_hc_cleanup(core_if, channel);
+ list_add_tail(&channel->hc_list_entry,
+ &hcd->free_hc_list);
+ }
+ }
+ }
+
+ /*
+ * A disconnect will end the session so the B-Device is no
+ * longer a B-host.
+ */
+ ((struct usb_hcd *)_p)->self.is_b_host = 0;
+ return 1;
+}
+
+/**
+ * Connection timeout function. An OTG host is required to display a
+ * message if the device does not connect within 10 seconds.
+ */
+static void dwc_otg_hcd_connect_timeout(unsigned long _ptr)
+{
+ pr_info("Connect Timeout\n");
+ pr_err("Device Not Connected/Responding\n");
+}
+
+/**
+ * Start the connection timer. An OTG host is required to display a
+ * message if the device does not connect within 10 seconds. The
+ * timer is deleted if a port connect interrupt occurs before the
+ * timer expires.
+ */
+static void dwc_otg_hcd_start_connect_timer(struct dwc_hcd *hcd)
+{
+ init_timer(&hcd->conn_timer);
+ hcd->conn_timer.function = dwc_otg_hcd_connect_timeout;
+ hcd->conn_timer.data = (unsigned long)0;
+ hcd->conn_timer.expires = jiffies + (HZ * 10);
+ add_timer(&hcd->conn_timer);
+}
+
+/**
+ * HCD Callback function for disconnect of the HCD.
+ */
+static int dwc_otg_hcd_session_start_cb(void *_p)
+{
+ struct dwc_hcd *hcd = hcd_to_dwc_otg_hcd(_p);
+
+ dwc_otg_hcd_start_connect_timer(hcd);
+ return 1;
+}
+
+/* HCD Callback structure for handling mode switching. */
+static struct cil_callbacks hcd_cil_callbacks = {
+ .start = dwc_otg_hcd_start_cb,
+ .stop = dwc_otg_hcd_stop_cb,
+ .disconnect = dwc_otg_hcd_disconnect_cb,
+ .session_start = dwc_otg_hcd_session_start_cb,
+ .p = NULL,
+};
+
+/*
+ * Reset Workqueue implementation
+ */
+static void port_reset_wqfunc(struct work_struct *work)
+{
+ struct dwc_hcd *hcd = container_of(work, struct dwc_hcd,
+ usb_port_reset);
+ struct core_if *core_if = hcd->core_if;
+ u32 hprt0 = 0;
+ unsigned long flags;
+
+ pr_info("%s\n", __func__);
+ spin_lock_irqsave(&hcd->lock, flags);
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ msleep(60);
+ spin_lock_irqsave(&hcd->lock, flags);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ spin_unlock_irqrestore(&hcd->lock, flags);
+}
+
+/*
+ * Wakeup Workqueue implementation
+ */
+static void port_wakeup_wqfunc(struct work_struct *work)
+{
+ struct core_if *core_if = container_of(to_delayed_work(work),
+ struct core_if, usb_port_wakeup);
+ u32 hprt0;
+
+ pr_info("%s\n", __func__);
+ /* Now wait for 70 ms. */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ msleep(70);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+}
+
+/**
+ * Starts processing a USB transfer request specified by a USB Request Block
+ * (URB). mem_flags indicates the type of memory allocation to use while
+ * processing this URB.
+ */
+static int dwc_otg_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
+ gfp_t _mem_flags)
+{
+ int retval;
+ unsigned long flags;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ struct dwc_qtd *qtd;
+
+ qtd = dwc_otg_hcd_qtd_create(urb, _mem_flags);
+ if (!qtd) {
+ pr_err("DWC OTG HCD URB Enqueue failed creating " "QTD\n");
+ retval = -ENOMEM;
+ goto err_enq;
+ }
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ retval = usb_hcd_link_urb_to_ep(hcd, urb);
+ if (unlikely(retval))
+ goto fail;
+
+ retval = dwc_otg_hcd_qtd_add(qtd, dwc_hcd);
+ if (retval < 0) {
+ pr_err("DWC OTG HCD URB Enqueue failed adding QTD. "
+ "Error status %d\n", retval);
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ goto fail;
+ }
+
+fail:
+ if (retval)
+ dwc_otg_hcd_qtd_free(qtd);
+
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+err_enq:
+
+ return retval;
+}
+
+/**
+ * Attempts to halt a host channel. This function should only be called in
+ * Slave mode or to abort a transfer in either Slave mode or DMA mode. Under
+ * normal circumstances in DMA mode, the controller halts the channel when the
+ * transfer is complete or a condition occurs that requires application
+ * intervention.
+ *
+ * In slave mode, checks for a free request queue entry, then sets the Channel
+ * Enable and Channel Disable bits of the Host Channel Characteristics
+ * register of the specified channel to intiate the halt. If there is no free
+ * request queue entry, sets only the Channel Disable bit of the HCCHARn
+ * register to flush requests for this channel. In the latter case, sets a
+ * flag to indicate that the host channel needs to be halted when a request
+ * queue slot is open.
+ *
+ * In DMA mode, always sets the Channel Enable and Channel Disable bits of the
+ * HCCHARn register. The controller ensures there is space in the request
+ * queue before submitting the halt request.
+ *
+ * Some time may elapse before the core flushes any posted requests for this
+ * host channel and halts. The Channel Halted interrupt handler completes the
+ * deactivation of the host channel.
+ */
+void dwc_otg_hc_halt(struct core_if *core_if, struct dwc_hc *hc,
+ enum dwc_halt_status hlt_sts)
+{
+ u32 nptxsts;
+ u32 hptxsts = 0;
+ u32 hcchar;
+ ulong hc_regs;
+ ulong global_regs = core_if->core_global_regs;
+ ulong host_global_regs;
+
+ hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+ host_global_regs = core_if->host_if->host_global_regs;
+
+ WARN_ON(hlt_sts == DWC_OTG_HC_XFER_NO_HALT_STATUS);
+
+ if (hlt_sts == DWC_OTG_HC_XFER_URB_DEQUEUE ||
+ hlt_sts == DWC_OTG_HC_XFER_AHB_ERR) {
+ /*
+ * Disable all channel interrupts except Ch Halted. The QTD
+ * and QH state associated with this transfer has been cleared
+ * (in the case of URB_DEQUEUE), so the channel needs to be
+ * shut down carefully to prevent crashes.
+ */
+ u32 hcintmsk;
+ hcintmsk = 0;
+ hcintmsk = DWC_HCINTMSK_CHAN_HALTED_RW(hcintmsk, 1);
+ dwc_reg_write(hc_regs, DWC_HCINTMSK, hcintmsk);
+
+ /*
+ * Make sure no other interrupts besides halt are currently
+ * pending. Handling another interrupt could cause a crash due
+ * to the QTD and QH state.
+ */
+ dwc_reg_write(hc_regs, DWC_HCINT, ~hcintmsk);
+
+ /*
+ * Make sure the halt status is set to URB_DEQUEUE or AHB_ERR
+ * even if the channel was already halted for some other reason.
+ */
+ hc->halt_status = hlt_sts;
+
+ /*
+ * If the channel is not enabled, the channel is either already
+ * halted or it hasn't started yet. In DMA mode, the transfer
+ * may halt if it finishes normally or a condition occurs that
+ * requires driver intervention. Don't want to halt the channel
+ * again. In either Slave or DMA mode, it's possible that the
+ * transfer has been assigned to a channel, but not started yet
+ * when an URB is dequeued. Don't want to halt a channel that
+ * hasn't started yet.
+ */
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ if (!DWC_HCCHAR_ENA_RD(hcchar))
+ return;
+ }
+
+ if (hc->halt_pending)
+ /*
+ * A halt has already been issued for this channel. This might
+ * happen when a transfer is aborted by a higher level in
+ * the stack.
+ */
+ return;
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 1);
+ if (!core_if->dma_enable) {
+ /* Check for space in the request queue to issue the halt. */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_CONTROL ||
+ hc->ep_type == DWC_OTG_EP_TYPE_BULK) {
+ nptxsts = dwc_reg_read(global_regs, DWC_GNPTXSTS);
+
+ if (!DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(nptxsts))
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0);
+ } else {
+ hptxsts =
+ dwc_reg_read(host_global_regs, DWC_HPTXSTS);
+
+ if (!DWC_HPTXSTS_PTXSPC_AVAIL_RD(hptxsts) ||
+ core_if->queuing_high_bandwidth)
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 0);
+ }
+ }
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ hc->halt_status = hlt_sts;
+ if (DWC_HCCHAR_ENA_RD(hcchar)) {
+ hc->halt_pending = 1;
+ hc->halt_on_queue = 0;
+ } else {
+ hc->halt_on_queue = 1;
+ }
+}
+
+/**
+ * Aborts/cancels a USB transfer request. Always returns 0 to indicate
+ * success.
+ */
+static int dwc_otg_hcd_urb_dequeue(struct usb_hcd *hcd, struct urb *urb,
+ int status)
+{
+ unsigned long flags;
+ struct dwc_hcd *dwc_hcd;
+ struct dwc_qtd *urb_qtd;
+ struct dwc_qh *qh;
+ int retval;
+
+ urb_qtd = (struct dwc_qtd *)urb->hcpriv;
+ if (!urb_qtd)
+ return -EINVAL;
+ qh = (struct dwc_qh *)urb_qtd->qtd_qh_ptr;
+ if (!qh)
+ return -EINVAL;
+
+ dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+
+ retval = usb_hcd_check_unlink_urb(hcd, urb, status);
+ if (retval) {
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return retval;
+ }
+
+ if (urb_qtd == qh->qtd_in_process) {
+ /*
+ * If still connected (i.e. in host mode), halt the
+ * channel so it can be used for other transfers. If
+ * no longer connected, the host registers can't be
+ * written to halt the channel since the core is in
+ * device mode.
+ */
+ dwc_otg_hc_halt(dwc_hcd->core_if, qh->channel,
+ DWC_OTG_HC_XFER_URB_DEQUEUE);
+
+ }
+
+ /*
+ * Free the QTD and clean up the associated QH. Leave the QH in the
+ * schedule if it has any remaining QTDs.
+ */
+ dwc_otg_hcd_qtd_remove_and_free(urb_qtd);
+ if (qh && urb_qtd == qh->qtd_in_process) {
+ dwc_otg_hcd_qh_deactivate(dwc_hcd, qh, 0);
+ qh->channel = NULL;
+ qh->qtd_in_process = NULL;
+ } else if (qh && list_empty(&qh->qtd_list)) {
+ dwc_otg_hcd_qh_remove(dwc_hcd, qh);
+ }
+
+ urb->hcpriv = NULL;
+ usb_hcd_unlink_urb_from_ep(hcd, urb);
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+
+ /* Higher layer software sets URB status. */
+ usb_hcd_giveback_urb(hcd, urb, status);
+
+ return 0;
+}
+
+/* Remove and free a QH */
+static inline void dwc_otg_hcd_qh_remove_and_free(struct dwc_hcd *hcd,
+ struct dwc_qh *qh)
+{
+ dwc_otg_hcd_qh_remove(hcd, qh);
+ dwc_otg_hcd_qh_free(qh);
+}
+
+static void qh_list_free(struct dwc_hcd *hcd, struct list_head *_qh_list)
+{
+ struct list_head *item, *tmp;
+ struct dwc_qh *qh;
+
+ /* If the list hasn't been initialized yet, return. */
+ if (_qh_list->next == NULL)
+ return;
+
+ /* Ensure there are no QTDs or URBs left. */
+ kill_urbs_in_qh_list(hcd, _qh_list);
+
+ list_for_each_safe(item, tmp, _qh_list) {
+ qh = list_entry(item, struct dwc_qh, qh_list_entry);
+ dwc_otg_hcd_qh_remove_and_free(hcd, qh);
+ }
+}
+
+/**
+ * Frees resources in the DWC_otg controller related to a given endpoint. Also
+ * clears state in the HCD related to the endpoint. Any URBs for the endpoint
+ * must already be dequeued.
+ */
+static void dwc_otg_hcd_endpoint_disable(struct usb_hcd *hcd,
+ struct usb_host_endpoint *ep)
+{
+ struct dwc_qh *qh;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ unsigned long flags;
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ qh = (struct dwc_qh *)ep->hcpriv;
+ if (qh) {
+ dwc_otg_hcd_qh_remove_and_free(dwc_hcd, qh);
+ ep->hcpriv = NULL;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+}
+
+/**
+ * Creates Status Change bitmap for the root hub and root port. The bitmap is
+ * returned in buf. Bit 0 is the status change indicator for the root hub. Bit 1
+ * is the status change indicator for the single root port. Returns 1 if either
+ * change indicator is 1, otherwise returns 0.
+ */
+
+static int dwc_otg_hcd_hub_status_data(struct dwc_hcd *hcd, char *buf)
+{
+ u32 hprt0;
+ hprt0 = dwc_reg_read(hcd->core_if->host_if->hprt0, 0);
+ buf[0] = 0;
+ buf[0] |= (DWC_HPRT0_PRT_STS_RD(hprt0)\
+ || DWC_HPRT0_PRT_RES_RD(hprt0)\
+ || DWC_HPRT0_PRT_ENA_RD(hprt0)\
+ || DWC_HPRT0_PRT_SUS_RD(hprt0)\
+ || DWC_HPRT0_PRT_OVRCURR_CHG_RD(hprt0));
+
+ return (buf[0] != 0);
+}
+
+/* Handles the hub class-specific ClearPortFeature request.*/
+static int do_clear_port_feature(struct dwc_hcd *hcd, u16 val)
+{
+ struct core_if *core_if = hcd->core_if;
+ u32 hprt0 = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hcd->lock, flags);
+ switch (val) {
+ case USB_PORT_FEAT_ENABLE:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_ENA_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_SUSPEND:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+
+ /* Clear Resume bit */
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ msleep(100);
+ spin_lock_irqsave(&hcd->lock, flags);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_POWER:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_INDICATOR:
+ /* Port inidicator not supported */
+ break;
+ case USB_PORT_FEAT_C_CONNECTION:
+ /* Clears drivers internal connect status change flag */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_STS_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_C_RESET:
+ /* Clears driver's internal Port Reset Change flag */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_RES_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_C_ENABLE:
+ /* Clears driver's internal Port Enable/Disable Change flag */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_ENA_DIS_CHG_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_C_SUSPEND:
+ /*
+ * Clears the driver's internal Port Suspend
+ * Change flag, which is set when resume signaling on
+ * the host port is complete
+ */
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_SPD_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_C_OVER_CURRENT:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_OVRCURR_CHG_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ default:
+ pr_err("DWC OTG HCD - ClearPortFeature request %xh "
+ "unknown or unsupported\n", val);
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ return -EINVAL;
+ }
+ spin_unlock_irqrestore(&hcd->lock, flags);
+ return 0;
+}
+
+/* Handles the hub class-specific SetPortFeature request.*/
+static int do_set_port_feature(struct usb_hcd *hcd, u16 val, u16 index)
+{
+ struct core_if *core_if = hcd_to_dwc_otg_hcd(hcd)->core_if;
+ u32 hprt0 = 0;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ unsigned long flags;
+ u32 pcgcctl = 0;
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+
+ switch (val) {
+ case USB_PORT_FEAT_SUSPEND:
+ if (hcd->self.otg_port == index && hcd->self.b_hnp_enable) {
+ u32 gotgctl = 0;
+ gotgctl |= DWC_GCTL_HOST_HNP_ENA;
+ dwc_reg_modify(core_if->core_global_regs,
+ DWC_GOTGCTL, 0, gotgctl);
+ core_if->xceiv->state = OTG_STATE_A_SUSPEND;
+ }
+
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_SUS_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+
+ /* Suspend the Phy Clock */
+ pcgcctl = DWC_PCGCCTL_STOP_CLK_SET(pcgcctl);
+ dwc_reg_write(core_if->pcgcctl, 0, pcgcctl);
+
+ /* For HNP the bus must be suspended for at least 200ms. */
+ if (hcd->self.b_hnp_enable) {
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ msleep(200);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ }
+ break;
+ case USB_PORT_FEAT_POWER:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+ hprt0 = DWC_HPRT0_PRT_PWR_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_RESET:
+ hprt0 = dwc_otg_read_hprt0(core_if);
+
+ /*
+ * When B-Host the Port reset bit is set in the Start HCD
+ * Callback function, so that the reset is started within 1ms
+ * of the HNP success interrupt.
+ */
+ if (!hcd->self.is_b_host) {
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 1);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ }
+
+ /* Clear reset bit in 10ms (FS/LS) or 50ms (HS) */
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ msleep(60);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ hprt0 = DWC_HPRT0_PRT_RST_RW(hprt0, 0);
+ dwc_reg_write(core_if->host_if->hprt0, 0, hprt0);
+ break;
+ case USB_PORT_FEAT_INDICATOR:
+ /* Not supported */
+ break;
+ default:
+ pr_err("DWC OTG HCD - "
+ "SetPortFeature request %xh "
+ "unknown or unsupported\n", val);
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return -EINVAL;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return 0;
+}
+
+/* Handles hub class-specific requests.*/
+static int dwc_otg_hcd_hub_control(struct usb_hcd *hcd, u16 req_type, u16 val,
+ u16 index, char *buf, u16 len)
+{
+ int retval = 0;
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ struct core_if *core_if = hcd_to_dwc_otg_hcd(hcd)->core_if;
+ struct usb_hub_descriptor *desc;
+ u32 hprt0 = 0;
+ u32 port_status;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ switch (req_type) {
+ case ClearHubFeature:
+ switch (val) {
+ case C_HUB_LOCAL_POWER:
+ case C_HUB_OVER_CURRENT:
+ /* Nothing required here */
+ break;
+ default:
+ retval = -EINVAL;
+ pr_err("DWC OTG HCD - ClearHubFeature request"
+ " %xh unknown\n", val);
+ }
+ break;
+ case ClearPortFeature:
+ if (!index || index > 1)
+ goto error;
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ retval = do_clear_port_feature(dwc_hcd, val);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ break;
+ case GetHubDescriptor:
+ desc = (struct usb_hub_descriptor *)buf;
+ desc->bDescLength = 9;
+ desc->bDescriptorType = 0x29;
+ desc->bNbrPorts = 1;
+ desc->wHubCharacteristics = 0x08;
+ desc->bPwrOn2PwrGood = 1;
+ desc->bHubContrCurrent = 0;
+ break;
+ case GetHubStatus:
+ memset(buf, 0, 4);
+ break;
+ case GetPortStatus:
+ if (!index || index > 1)
+ goto error;
+
+ port_status = 0;
+ hprt0 = dwc_reg_read(core_if->host_if->hprt0, 0);
+
+ if (DWC_HPRT0_PRT_STS_RD(hprt0))
+ port_status |= USB_PORT_STAT_CONNECTION;
+ if (DWC_HPRT0_PRT_ENA_RD(hprt0))
+ port_status |= USB_PORT_STAT_ENABLE;
+ if (DWC_HPRT0_PRT_SUS_RD(hprt0))
+ port_status |= USB_PORT_STAT_SUSPEND;
+ if (DWC_HPRT0_PRT_OVRCURR_ACT_RD(hprt0))
+ port_status |= USB_PORT_STAT_OVERCURRENT;
+ if (DWC_HPRT0_PRT_RST_RD(hprt0))
+ port_status |= USB_PORT_STAT_RESET;
+ if (DWC_HPRT0_PRT_PWR_RD(hprt0))
+ port_status |= USB_PORT_STAT_POWER;
+ if (DWC_HPRT0_PRT_SPD_RD(hprt0) == DWC_HPRT0_PRTSPD_HIGH_SPEED)
+ port_status |= USB_PORT_STAT_HIGH_SPEED;
+ else if (DWC_HPRT0_PRT_SPD_RD(hprt0) ==
+ DWC_HPRT0_PRTSPD_LOW_SPEED)
+ port_status |= USB_PORT_STAT_LOW_SPEED;
+
+ if (DWC_HPRT0_PRT_TST_CTL_RD(hprt0))
+ port_status |= (1 << USB_PORT_FEAT_TEST);
+
+ /* USB_PORT_FEAT_INDICATOR unsupported always 0 */
+ *((__le32 *) buf) = cpu_to_le32(port_status);
+ break;
+ case SetHubFeature:
+ /* No HUB features supported */
+ break;
+ case SetPortFeature:
+ if (val != USB_PORT_FEAT_TEST && (!index || index > 1))
+ goto error;
+
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ retval = do_set_port_feature(hcd, val, index);
+ spin_lock_irqsave(&dwc_hcd->lock, flags);
+ break;
+ default:
+error:
+ retval = -EINVAL;
+ pr_warning("DWC OTG HCD - Unknown hub control request"
+ " type or invalid req_type: %xh index: %xh "
+ "val: %xh\n", req_type, index, val);
+ break;
+ }
+ spin_unlock_irqrestore(&dwc_hcd->lock, flags);
+ return retval;
+}
+
+/**
+ * Handles host mode interrupts for the DWC_otg controller. Returns IRQ_NONE if
+ * there was no interrupt to handle. Returns IRQ_HANDLED if there was a valid
+ * interrupt.
+ *
+ * This function is called by the USB core when an interrupt occurs
+ */
+static irqreturn_t dwc_otg_hcd_irq(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+
+ return IRQ_RETVAL(dwc_otg_hcd_handle_intr(dwc_hcd));
+}
+
+static const struct hc_driver dwc_otg_hc_driver = {
+ .description = dwc_otg_hcd_name,
+ .product_desc = "DWC OTG Controller",
+ .hcd_priv_size = sizeof(struct dwc_hcd),
+ .irq = dwc_otg_hcd_irq,
+ .flags = HCD_MEMORY | HCD_USB2,
+ .start = dwc_otg_hcd_start,
+ .stop = dwc_otg_hcd_stop,
+ .urb_enqueue = dwc_otg_hcd_urb_enqueue,
+ .urb_dequeue = dwc_otg_hcd_urb_dequeue,
+ .endpoint_disable = dwc_otg_hcd_endpoint_disable,
+ .get_frame_number = dwc_otg_hcd_get_frame_number,
+ .hub_status_data = dwc_otg_hcd_hub_status_data,
+ .hub_control = dwc_otg_hcd_hub_control,
+};
+
+/**
+ * Frees secondary storage associated with the dwc_hcd structure contained
+ * in the struct usb_hcd field.
+ */
+static void dwc_otg_hcd_free(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ u32 i;
+
+ del_timers(dwc_hcd);
+
+ /* Free memory for QH/QTD lists */
+ qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_inactive);
+ qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_deferred);
+ qh_list_free(dwc_hcd, &dwc_hcd->non_periodic_sched_active);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_inactive);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_ready);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_assigned);
+ qh_list_free(dwc_hcd, &dwc_hcd->periodic_sched_queued);
+
+ /* Free memory for the host channels. */
+ for (i = 0; i < MAX_EPS_CHANNELS; i++) {
+ struct dwc_hc *hc = dwc_hcd->hc_ptr_array[i];
+
+ kfree(hc);
+ }
+ if (dwc_hcd->core_if->dma_enable) {
+ if (dwc_hcd->status_buf_dma)
+ dma_free_coherent(hcd->self.controller,
+ DWC_OTG_HCD_STATUS_BUF_SIZE,
+ dwc_hcd->status_buf,
+ dwc_hcd->status_buf_dma);
+ } else {
+ kfree(dwc_hcd->status_buf);
+ }
+
+}
+
+/**
+ * Initializes the HCD. This function allocates memory for and initializes the
+ * static parts of the usb_hcd and dwc_hcd structures. It also registers the
+ * USB bus with the core and calls the hc_driver->start() function. It returns
+ * a negative error on failure.
+ */
+int __devinit dwc_otg_hcd_init(struct device *_dev,
+ struct dwc_otg_device *dwc_otg_device)
+{
+ struct usb_hcd *hcd;
+ struct dwc_hcd *dwc_hcd;
+ struct dwc_otg_device *otg_dev = dev_get_drvdata(_dev);
+ int num_channels;
+ u32 i;
+ struct dwc_hc *channel;
+ int retval = 0;
+
+ /*
+ * Allocate memory for the base HCD plus the DWC OTG HCD.
+ * Initialize the base HCD.
+ */
+ hcd = usb_create_hcd(&dwc_otg_hc_driver, _dev, dwc_otg_hcd_name);
+ if (!hcd) {
+ retval = -ENOMEM;
+ goto error1;
+ }
+ dev_set_drvdata(_dev, dwc_otg_device);
+ hcd->regs = otg_dev->base;
+ hcd->rsrc_start = otg_dev->phys_addr;
+ hcd->rsrc_len = otg_dev->base_len;
+ hcd->self.otg_port = 1;
+
+ /* Initialize the DWC OTG HCD. */
+ dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ dwc_hcd->core_if = otg_dev->core_if;
+ spin_lock_init(&dwc_hcd->lock);
+ otg_dev->hcd = dwc_hcd;
+
+ /* Register the HCD CIL Callbacks */
+ dwc_otg_cil_register_hcd_callbacks(otg_dev->core_if, &hcd_cil_callbacks,
+ hcd);
+
+ /* Initialize the non-periodic schedule. */
+ INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_inactive);
+ INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_active);
+ INIT_LIST_HEAD(&dwc_hcd->non_periodic_sched_deferred);
+
+ /* Initialize the periodic schedule. */
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_inactive);
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_ready);
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_assigned);
+ INIT_LIST_HEAD(&dwc_hcd->periodic_sched_queued);
+
+ /*
+ * Create a host channel descriptor for each host channel implemented
+ * in the controller. Initialize the channel descriptor array.
+ */
+ INIT_LIST_HEAD(&dwc_hcd->free_hc_list);
+ num_channels = dwc_hcd->core_if->core_params->host_channels;
+
+ for (i = 0; i < num_channels; i++) {
+ channel = kzalloc(sizeof(struct dwc_hc), GFP_KERNEL);
+ if (!channel) {
+ retval = -ENOMEM;
+ pr_err("%s: host channel allocation failed\n",
+ __func__);
+ goto error2;
+ }
+
+ channel->hc_num = i;
+ dwc_hcd->hc_ptr_array[i] = channel;
+ }
+
+ /* Initialize the Connection timeout timer. */
+ init_timer(&dwc_hcd->conn_timer);
+
+ /* Initialize workqueue */
+ INIT_WORK(&dwc_hcd->usb_port_reset, port_reset_wqfunc);
+ INIT_WORK(&dwc_hcd->start_work, hcd_start_func);
+ INIT_WORK(&dwc_hcd->core_if->usb_port_otg, NULL);
+ INIT_DELAYED_WORK(&dwc_hcd->core_if->usb_port_wakeup,
+ port_wakeup_wqfunc);
+
+ /* Set device flags indicating whether the HCD supports DMA. */
+ if (otg_dev->core_if->dma_enable) {
+ static u64 dummy_mask = DMA_BIT_MASK(32);
+
+ pr_info("Using DMA mode\n");
+ _dev->dma_mask = (void *)&dummy_mask;
+ _dev->coherent_dma_mask = ~0;
+ } else {
+ pr_info("Using Slave mode\n");
+ _dev->dma_mask = (void *)0;
+ _dev->coherent_dma_mask = 0;
+ }
+
+ init_hcd_usecs(dwc_hcd);
+ /*
+ * Finish generic HCD initialization and start the HCD. This function
+ * allocates the DMA buffer pool, registers the USB bus, requests the
+ * IRQ line, and calls dwc_otg_hcd_start method.
+ */
+ retval = usb_add_hcd(hcd, otg_dev->irq, IRQF_SHARED);
+ if (retval < 0)
+ goto error2;
+ hcd->rsrc_start = otg_dev->phys_addr;
+ hcd->rsrc_len = otg_dev->base_len;
+
+ /*
+ * Allocate space for storing data on status transactions. Normally no
+ * data is sent, but this space acts as a bit bucket. This must be
+ * done after usb_add_hcd since that function allocates the DMA buffer
+ * pool.
+ */
+ if (otg_dev->core_if->dma_enable) {
+ dwc_hcd->status_buf =
+ dma_alloc_coherent(_dev, DWC_OTG_HCD_STATUS_BUF_SIZE,
+ &dwc_hcd->status_buf_dma,
+ GFP_KERNEL | GFP_DMA);
+ } else {
+ dwc_hcd->status_buf = kmalloc(DWC_OTG_HCD_STATUS_BUF_SIZE,
+ GFP_KERNEL);
+ }
+ if (!dwc_hcd->status_buf) {
+ retval = -ENOMEM;
+ pr_err("%s: status_buf allocation failed\n", __func__);
+ goto error3;
+ }
+ return 0;
+
+error3:
+ usb_remove_hcd(hcd);
+error2:
+ dwc_otg_hcd_free(hcd);
+ usb_put_hcd(hcd);
+error1:
+ return retval;
+}
+
+/**
+ * Removes the HCD.
+ * Frees memory and resources associated with the HCD and deregisters the bus.
+ */
+void __devexit dwc_otg_hcd_remove(struct device *_dev)
+{
+ struct dwc_otg_device *otg_dev = dev_get_drvdata(_dev);
+ struct dwc_hcd *dwc_hcd = otg_dev->hcd;
+ struct usb_hcd *hcd = dwc_otg_hcd_to_hcd(dwc_hcd);
+
+ /* Turn off all interrupts */
+ dwc_reg_write(gintmsk_reg(dwc_hcd), 0, 0);
+ spin_lock(&dwc_hcd->lock);
+ dwc_reg_modify(gahbcfg_reg(dwc_hcd), 0, 1, 0);
+ spin_unlock(&dwc_hcd->lock);
+
+ cancel_work_sync(&dwc_hcd->start_work);
+ cancel_work_sync(&dwc_hcd->usb_port_reset);
+ cancel_work_sync(&dwc_hcd->core_if->usb_port_otg);
+
+ usb_remove_hcd(hcd);
+ dwc_otg_hcd_free(hcd);
+ usb_put_hcd(hcd);
+}
+
+/** Returns the current frame number. */
+int dwc_otg_hcd_get_frame_number(struct usb_hcd *hcd)
+{
+ struct dwc_hcd *dwc_hcd = hcd_to_dwc_otg_hcd(hcd);
+ u32 hfnum = 0;
+
+ hfnum = dwc_reg_read(dwc_hcd->core_if->host_if->
+ host_global_regs, DWC_HFNUM);
+
+ return DWC_HFNUM_FRNUM_RD(hfnum);
+}
+
+/**
+ * Prepares a host channel for transferring packets to/from a specific
+ * endpoint. The HCCHARn register is set up with the characteristics specified
+ * in _hc. Host channel interrupts that may need to be serviced while this
+ * transfer is in progress are enabled.
+ */
+static void dwc_otg_hc_init(struct core_if *core_if, struct dwc_hc *hc)
+{
+ u32 intr_enable;
+ ulong global_regs = core_if->core_global_regs;
+ u32 hc_intr_mask = 0;
+ u32 gintmsk = 0;
+ u32 hcchar;
+ u32 hcsplt;
+ u8 hc_num = hc->hc_num;
+ struct dwc_host_if *host_if = core_if->host_if;
+ ulong hc_regs = host_if->hc_regs[hc_num];
+
+ /* Clear old interrupt conditions for this host channel. */
+ hc_intr_mask = 0x3FF;
+ dwc_reg_write(hc_regs, DWC_HCINT, hc_intr_mask);
+
+ /* Enable channel interrupts required for this transfer. */
+ hc_intr_mask = 0;
+ hc_intr_mask = DWC_HCINTMSK_CHAN_HALTED_RW(hc_intr_mask, 1);
+ if (core_if->dma_enable) {
+ hc_intr_mask = DWC_HCINTMSK_AHB_ERR_RW(hc_intr_mask, 1);
+
+ if (hc->error_state && !hc->do_split &&
+ hc->ep_type != DWC_OTG_EP_TYPE_ISOC) {
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask, 1);
+ if (hc->ep_is_in) {
+ hc_intr_mask =
+ DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask,
+ 1);
+ if (hc->ep_type != DWC_OTG_EP_TYPE_INTR)
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+ }
+ } else {
+ switch (hc->ep_type) {
+ case DWC_OTG_EP_TYPE_CONTROL:
+ case DWC_OTG_EP_TYPE_BULK:
+ hc_intr_mask =
+ DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_STALL_RESP_REC_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask, 1);
+
+ if (hc->ep_is_in) {
+ hc_intr_mask =
+ DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1);
+ } else {
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask,
+ 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_NYET_RESP_REC_RW(hc_intr_mask,
+ 1);
+ if (hc->do_ping)
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+
+ if (hc->do_split) {
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask,
+ 1);
+ if (hc->complete_split)
+ hc_intr_mask =
+ DWC_HCINTMSK_NYET_RESP_REC_RW
+ (hc_intr_mask, 1);
+ else
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+
+ if (hc->error_state)
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask,
+ 1);
+ break;
+ case DWC_OTG_EP_TYPE_INTR:
+ hc_intr_mask =
+ DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_NAK_RESP_REC_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_STALL_RESP_REC_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_DATA_TOG_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hc_intr_mask, 1);
+
+ if (hc->ep_is_in)
+ hc_intr_mask =
+ DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1);
+ if (hc->error_state)
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask,
+ 1);
+
+ if (hc->do_split) {
+ if (hc->complete_split)
+ hc_intr_mask =
+ DWC_HCINTMSK_NYET_RESP_REC_RW
+ (hc_intr_mask, 1);
+ else
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW
+ (hc_intr_mask, 1);
+ }
+ break;
+ case DWC_OTG_EP_TYPE_ISOC:
+ hc_intr_mask =
+ DWC_HCINTMSK_TXFER_CMPL_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_FRAME_OVERN_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_ACK_RESP_REC_RW(hc_intr_mask, 1);
+
+ if (hc->ep_is_in) {
+ hc_intr_mask =
+ DWC_HCINTMSK_TRANS_ERR_RW(hc_intr_mask, 1);
+ hc_intr_mask =
+ DWC_HCINTMSK_BBL_ERR_RW(hc_intr_mask, 1);
+ }
+ break;
+ }
+ }
+ dwc_reg_write(hc_regs, DWC_HCINTMSK, hc_intr_mask);
+
+ /* Enable the top level host channel interrupt. */
+ intr_enable = (1 << hc_num);
+ dwc_reg_modify(host_if->host_global_regs, DWC_HAINTMSK, 0,
+ intr_enable);
+
+ /* Make sure host channel interrupts are enabled. */
+ gintmsk |= DWC_INTMSK_HST_CHAN;
+ dwc_reg_modify(global_regs, DWC_GINTMSK, 0, gintmsk);
+
+ /*
+ * Program the HCCHARn register with the endpoint characteristics for
+ * the current transfer.
+ */
+ hcchar = 0;
+ hcchar = DWC_HCCHAR_DEV_ADDR_RW(hcchar, hc->dev_addr);
+ hcchar = DWC_HCCHAR_EP_NUM_RW(hcchar, hc->ep_num);
+ hcchar = DWC_HCCHAR_EPDIR_RW(hcchar, hc->ep_is_in);
+ hcchar = DWC_HCCHAR_LSP_DEV_RW(hcchar, (hc->speed ==
+ DWC_OTG_EP_SPEED_LOW));
+ hcchar = DWC_HCCHAR_EPTYPE_RW(hcchar, hc->ep_type);
+ hcchar = DWC_HCCHAR_MPS_RW(hcchar, hc->max_packet);
+ dwc_reg_write(host_if->hc_regs[hc_num], DWC_HCCHAR, hcchar);
+
+ /* Program the HCSPLIT register for SPLITs */
+ hcsplt = 0;
+ if (hc->do_split) {
+ hcsplt = DWC_HCSPLT_COMP_SPLT_RW(hcsplt, hc->complete_split);
+ hcsplt = DWC_HCSPLT_TRANS_POS_RW(hcsplt, hc->xact_pos);
+ hcsplt = DWC_HCSPLT_HUB_ADDR_RW(hcsplt, hc->hub_addr);
+ hcsplt = DWC_HCSPLT_PRT_ADDR_RW(hcsplt, hc->port_addr);
+ }
+ dwc_reg_write(host_if->hc_regs[hc_num], DWC_HCSPLT, hcsplt);
+}
+
+/**
+ * Assigns transactions from a QTD to a free host channel and initializes the
+ * host channel to perform the transactions. The host channel is removed from
+ * the free list.
+ */
+static void assign_and_init_hc(struct dwc_hcd *hcd, struct dwc_qh *qh)
+{
+ struct dwc_hc *hc;
+ struct dwc_qtd *qtd;
+ struct urb *urb;
+ struct usb_iso_packet_descriptor *frame_desc;
+
+ hc = list_entry(hcd->free_hc_list.next, struct dwc_hc, hc_list_entry);
+
+ /* Remove the host channel from the free list. */
+ list_del_init(&hc->hc_list_entry);
+ qtd = list_entry(qh->qtd_list.next, struct dwc_qtd, qtd_list_entry);
+ urb = qtd->urb;
+ qh->channel = hc;
+ qh->qtd_in_process = qtd;
+
+ /*
+ * Use usb_pipedevice to determine device address. This address is
+ * 0 before the SET_ADDRESS command and the correct address afterward.
+ */
+ hc->dev_addr = usb_pipedevice(urb->pipe);
+ hc->ep_num = usb_pipeendpoint(urb->pipe);
+
+ if (urb->dev->speed == USB_SPEED_LOW)
+ hc->speed = DWC_OTG_EP_SPEED_LOW;
+ else if (urb->dev->speed == USB_SPEED_FULL)
+ hc->speed = DWC_OTG_EP_SPEED_FULL;
+ else
+ hc->speed = DWC_OTG_EP_SPEED_HIGH;
+
+ hc->max_packet = dwc_max_packet(qh->maxp);
+ hc->xfer_started = 0;
+ hc->halt_status = DWC_OTG_HC_XFER_NO_HALT_STATUS;
+ hc->error_state = (qtd->error_count > 0);
+ hc->halt_on_queue = 0;
+ hc->halt_pending = 0;
+ hc->requests = 0;
+
+ /*
+ * The following values may be modified in the transfer type section
+ * below. The xfer_len value may be reduced when the transfer is
+ * started to accommodate the max widths of the XferSize and PktCnt
+ * fields in the HCTSIZn register.
+ */
+ hc->do_ping = qh->ping_state;
+ hc->ep_is_in = (usb_pipein(urb->pipe) != 0);
+ hc->data_pid_start = qh->data_toggle;
+ hc->multi_count = 1;
+
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff = urb->transfer_dma + (u8 *) urb->actual_length;
+ else
+ hc->xfer_buff = (u8 *) urb->transfer_buffer +
+ urb->actual_length;
+
+ hc->xfer_len = urb->transfer_buffer_length - urb->actual_length;
+ hc->xfer_count = 0;
+
+ /*
+ * Set the split attributes
+ */
+ hc->do_split = 0;
+ if (qh->do_split) {
+ hc->do_split = 1;
+ hc->xact_pos = qtd->isoc_split_pos;
+ hc->complete_split = qtd->complete_split;
+ hc->hub_addr = urb->dev->tt->hub->devnum;
+ hc->port_addr = urb->dev->ttport;
+ }
+
+ switch (usb_pipetype(urb->pipe)) {
+ case PIPE_CONTROL:
+ hc->ep_type = DWC_OTG_EP_TYPE_CONTROL;
+
+ switch (qtd->control_phase) {
+ case DWC_OTG_CONTROL_SETUP:
+ hc->do_ping = 0;
+ hc->ep_is_in = 0;
+ hc->data_pid_start = DWC_OTG_HC_PID_SETUP;
+
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff = (u8 *) (u32) urb->setup_dma;
+ else
+ hc->xfer_buff = (u8 *) urb->setup_packet;
+
+ hc->xfer_len = 8;
+ break;
+ case DWC_OTG_CONTROL_DATA:
+ hc->data_pid_start = qtd->data_toggle;
+ break;
+ case DWC_OTG_CONTROL_STATUS:
+ /*
+ * Direction is opposite of data direction or IN if no
+ * data.
+ */
+ if (urb->transfer_buffer_length == 0)
+ hc->ep_is_in = 1;
+ else
+ hc->ep_is_in = (usb_pipein(urb->pipe) !=
+ USB_DIR_IN);
+
+ if (hc->ep_is_in)
+ hc->do_ping = 0;
+
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA1;
+ hc->xfer_len = 0;
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff =
+ (u8 *) (u32) hcd->status_buf_dma;
+ else
+ hc->xfer_buff = (u8 *) hcd->status_buf;
+ break;
+ }
+ break;
+ case PIPE_BULK:
+ hc->ep_type = DWC_OTG_EP_TYPE_BULK;
+ break;
+ case PIPE_INTERRUPT:
+ hc->ep_type = DWC_OTG_EP_TYPE_INTR;
+ break;
+ case PIPE_ISOCHRONOUS:
+ frame_desc = &urb->iso_frame_desc[qtd->isoc_frame_index];
+ hc->ep_type = DWC_OTG_EP_TYPE_ISOC;
+
+ if (hcd->core_if->dma_enable)
+ hc->xfer_buff = (u8 *) (u32) urb->transfer_dma;
+ else
+ hc->xfer_buff = (u8 *) urb->transfer_buffer;
+
+ hc->xfer_buff += frame_desc->offset + qtd->isoc_split_offset;
+ hc->xfer_len = frame_desc->length - qtd->isoc_split_offset;
+
+ if (hc->xact_pos == DWC_HCSPLIT_XACTPOS_ALL) {
+ if (hc->xfer_len <= 188)
+ hc->xact_pos = DWC_HCSPLIT_XACTPOS_ALL;
+ else
+ hc->xact_pos = DWC_HCSPLIT_XACTPOS_BEGIN;
+ }
+ break;
+ }
+
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+ /*
+ * This value may be modified when the transfer is started to
+ * reflect the actual transfer length.
+ */
+ hc->multi_count = dwc_hb_mult(qh->maxp);
+
+ dwc_otg_hc_init(hcd->core_if, hc);
+ hc->qh = qh;
+}
+
+/**
+ * This function selects transactions from the HCD transfer schedule and
+ * assigns them to available host channels. It is called from HCD interrupt
+ * handler functions.
+ */
+enum dwc_transaction_type dwc_otg_hcd_select_transactions(struct dwc_hcd *hcd)
+{
+ struct list_head *qh_ptr;
+ struct dwc_qh *qh;
+ int num_channels;
+ enum dwc_transaction_type ret_val = DWC_OTG_TRANSACTION_NONE;
+
+ /* Process entries in the periodic ready list. */
+ num_channels = hcd->core_if->core_params->host_channels;
+ qh_ptr = hcd->periodic_sched_ready.next;
+ while (qh_ptr != &hcd->periodic_sched_ready &&
+ !list_empty(&hcd->free_hc_list)) {
+ /* Leave one channel for non periodic transactions. */
+ if (hcd->available_host_channels <= 1)
+ break;
+ hcd->available_host_channels--;
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+ assign_and_init_hc(hcd, qh);
+ /*
+ * Move the QH from the periodic ready schedule to the
+ * periodic assigned schedule.
+ */
+ qh_ptr = qh_ptr->next;
+ list_move(&qh->qh_list_entry, &hcd->periodic_sched_assigned);
+ ret_val = DWC_OTG_TRANSACTION_PERIODIC;
+ }
+
+ /*
+ * Process entries in the deferred portion of the non-periodic list.
+ * A NAK put them here and, at the right time, they need to be
+ * placed on the sched_inactive list.
+ */
+ qh_ptr = hcd->non_periodic_sched_deferred.next;
+ while (qh_ptr != &hcd->non_periodic_sched_deferred) {
+ u16 frame_number =
+ dwc_otg_hcd_get_frame_number(dwc_otg_hcd_to_hcd(hcd));
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+ qh_ptr = qh_ptr->next;
+
+ if (dwc_frame_num_le(qh->sched_frame, frame_number))
+ /*
+ * Move the QH from the non periodic deferred schedule
+ * to the non periodic inactive schedule.
+ */
+ list_move(&qh->qh_list_entry,
+ &hcd->non_periodic_sched_inactive);
+ }
+
+ /*
+ * Process entries in the inactive portion of the non-periodic
+ * schedule. Some free host channels may not be used if they are
+ * reserved for periodic transfers.
+ */
+ qh_ptr = hcd->non_periodic_sched_inactive.next;
+ num_channels = hcd->core_if->core_params->host_channels;
+
+ while (qh_ptr != &hcd->non_periodic_sched_inactive
+ && !list_empty(&hcd->free_hc_list)) {
+ if (hcd->available_host_channels < 1)
+ break;
+ hcd->available_host_channels--;
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+ assign_and_init_hc(hcd, qh);
+ /*
+ * Move the QH from the non-periodic inactive schedule to the
+ * non-periodic active schedule.
+ */
+ qh_ptr = qh_ptr->next;
+ list_move(&qh->qh_list_entry, &hcd->non_periodic_sched_active);
+ if (ret_val == DWC_OTG_TRANSACTION_NONE)
+ ret_val = DWC_OTG_TRANSACTION_NON_PERIODIC;
+ else
+ ret_val = DWC_OTG_TRANSACTION_ALL;
+
+ }
+ return ret_val;
+}
+
+/**
+ * Sets the channel property that indicates in which frame a periodic transfer
+ * should occur. This is always set to the _next_ frame. This function has no
+ * effect on non-periodic transfers.
+ */
+static inline void hc_set_even_odd_frame(struct core_if *core_if,
+ struct dwc_hc *hc, u32 * hcchar)
+{
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ u32 hfnum = 0;
+
+ hfnum = dwc_reg_read(core_if->host_if->host_global_regs,
+ DWC_HFNUM);
+
+ /* 1 if _next_ frame is odd, 0 if it's even */
+ *hcchar = DWC_HCCHAR_ODD_FRAME_RW(*hcchar,
+ ((DWC_HFNUM_FRNUM_RD(hfnum) &
+ 0x1) ? 0 : 1));
+ }
+}
+
+static void set_initial_xfer_pid(struct dwc_hc *hc)
+{
+ if (hc->speed == DWC_OTG_EP_SPEED_HIGH) {
+ if (hc->ep_is_in) {
+ if (hc->multi_count == 1)
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+ else if (hc->multi_count == 2)
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA1;
+ else
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA2;
+ } else {
+ if (hc->multi_count == 1)
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+ else
+ hc->data_pid_start = DWC_OTG_HC_PID_MDATA;
+ }
+ } else {
+ hc->data_pid_start = DWC_OTG_HC_PID_DATA0;
+ }
+}
+
+/**
+ * Starts a PING transfer. This function should only be called in Slave mode.
+ * The Do Ping bit is set in the HCTSIZ register, then the channel is enabled.
+ */
+static void dwc_otg_hc_do_ping(struct core_if *core_if, struct dwc_hc *hc)
+{
+ u32 hcchar;
+ u32 hctsiz = 0;
+
+ ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+
+ hctsiz = 0;
+ hctsiz = DWC_HCTSIZ_DO_PING_PROTO_RW(hctsiz, 1);
+ hctsiz = DWC_HCTSIZ_PKT_CNT_RW(hctsiz, 1);
+ dwc_reg_write(hc_regs, DWC_HCTSIZ, hctsiz);
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+}
+
+/**
+ * This function writes a packet into the Tx FIFO associated with the Host
+ * Channel. For a channel associated with a non-periodic EP, the non-periodic
+ * Tx FIFO is written. For a channel associated with a periodic EP, the
+ * periodic Tx FIFO is written. This function should only be called in Slave
+ * mode.
+ *
+ * Upon return the xfer_buff and xfer_count fields in hc are incremented by
+ * then number of bytes written to the Tx FIFO.
+ */
+static void dwc_otg_hc_write_packet(struct core_if *core_if, struct dwc_hc *hc)
+{
+ u32 i;
+ u32 remaining_count;
+ u32 byte_count;
+ u32 dword_count;
+ u32 *data_buff = (u32 *) (hc->xfer_buff);
+ u32 data_fifo = core_if->data_fifo[hc->hc_num];
+
+ remaining_count = hc->xfer_len - hc->xfer_count;
+ if (remaining_count > hc->max_packet)
+ byte_count = hc->max_packet;
+ else
+ byte_count = remaining_count;
+
+ dword_count = (byte_count + 3) / 4;
+
+ if (((unsigned long)data_buff) & 0x3)
+ /* xfer_buff is not DWORD aligned. */
+ for (i = 0; i < dword_count; i++, data_buff++)
+ dwc_write_fifo32(data_fifo,
+ get_unaligned(data_buff));
+ else
+ /* xfer_buff is DWORD aligned. */
+ for (i = 0; i < dword_count; i++, data_buff++)
+ dwc_write_fifo32(data_fifo, *data_buff);
+
+ hc->xfer_count += byte_count;
+ hc->xfer_buff += byte_count;
+}
+
+/**
+ * This function does the setup for a data transfer for a host channel and
+ * starts the transfer. May be called in either Slave mode or DMA mode. In
+ * Slave mode, the caller must ensure that there is sufficient space in the
+ * request queue and Tx Data FIFO.
+ *
+ * For an OUT transfer in Slave mode, it loads a data packet into the
+ * appropriate FIFO. If necessary, additional data packets will be loaded in
+ * the Host ISR.
+ *
+ * For an IN transfer in Slave mode, a data packet is requested. The data
+ * packets are unloaded from the Rx FIFO in the Host ISR. If necessary,
+ * additional data packets are requested in the Host ISR.
+ *
+ * For a PING transfer in Slave mode, the Do Ping bit is set in the HCTSIZ
+ * register along with a packet count of 1 and the channel is enabled. This
+ * causes a single PING transaction to occur. Other fields in HCTSIZ are
+ * simply set to 0 since no data transfer occurs in this case.
+ *
+ * For a PING transfer in DMA mode, the HCTSIZ register is initialized with
+ * all the information required to perform the subsequent data transfer. In
+ * addition, the Do Ping bit is set in the HCTSIZ register. In this case, the
+ * controller performs the entire PING protocol, then starts the data
+ * transfer.
+ */
+static void dwc_otg_hc_start_transfer(struct core_if *core_if,
+ struct dwc_hc *hc)
+{
+ u32 hcchar;
+ u32 hctsiz = 0;
+ u16 num_packets;
+ u32 max_hc_xfer_size = core_if->core_params->max_transfer_size;
+ u16 max_hc_pkt_count = core_if->core_params->max_packet_count;
+ ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+ hctsiz = 0;
+
+ if (hc->do_ping) {
+ if (!core_if->dma_enable) {
+ dwc_otg_hc_do_ping(core_if, hc);
+ hc->xfer_started = 1;
+ return;
+ } else {
+ hctsiz = DWC_HCTSIZ_DO_PING_PROTO_RW(hctsiz, 1);
+ }
+ }
+
+ if (hc->do_split) {
+ num_packets = 1;
+
+ if (hc->complete_split && !hc->ep_is_in)
+ /*
+ * For CSPLIT OUT Transfer, set the size to 0 so the
+ * core doesn't expect any data written to the FIFO
+ */
+ hc->xfer_len = 0;
+ else if (hc->ep_is_in || (hc->xfer_len > hc->max_packet))
+ hc->xfer_len = hc->max_packet;
+ else if (!hc->ep_is_in && (hc->xfer_len > 188))
+ hc->xfer_len = 188;
+
+ hctsiz = DWC_HCTSIZ_XFER_SIZE_RW(hctsiz, hc->xfer_len);
+ } else {
+ /*
+ * Ensure that the transfer length and packet count will fit
+ * in the widths allocated for them in the HCTSIZn register.
+ */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ u32 max_len = hc->multi_count * hc->max_packet;
+
+ /*
+ * Make sure the transfer size is no larger than one
+ * (micro)frame's worth of data. (A check was done
+ * when the periodic transfer was accepted to ensure
+ * that a (micro)frame's worth of data can be
+ * programmed into a channel.)
+ */
+ if (hc->xfer_len > max_len)
+ hc->xfer_len = max_len;
+ } else if (hc->xfer_len > max_hc_xfer_size) {
+ /*
+ * Make sure that xfer_len is a multiple of max packet
+ * size.
+ */
+ hc->xfer_len = max_hc_xfer_size - hc->max_packet + 1;
+ }
+ if (hc->xfer_len > 0) {
+ num_packets = (hc->xfer_len + hc->max_packet - 1) /
+ hc->max_packet;
+ if (num_packets > max_hc_pkt_count) {
+ num_packets = max_hc_pkt_count;
+ hc->xfer_len = num_packets * hc->max_packet;
+ }
+ } else {
+ /* Need 1 packet for transfer length of 0. */
+ num_packets = 1;
+ }
+
+ if (hc->ep_is_in)
+ /*
+ * Always program an integral # of max packets for IN
+ * transfers.
+ */
+ hc->xfer_len = num_packets * hc->max_packet;
+
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+ /*
+ * Make sure that the multi_count field matches the
+ * actual transfer length.
+ */
+ hc->multi_count = num_packets;
+
+ /* Set up the initial PID for the transfer. */
+ if (hc->ep_type == DWC_OTG_EP_TYPE_ISOC)
+ set_initial_xfer_pid(hc);
+
+ hctsiz = DWC_HCTSIZ_XFER_SIZE_RW(hctsiz, hc->xfer_len);
+ }
+
+ hc->start_pkt_count = num_packets;
+ hctsiz = DWC_HCTSIZ_PKT_CNT_RW(hctsiz, num_packets);
+ hctsiz = DWC_HCTSIZ_PKT_PID_RW(hctsiz, hc->data_pid_start);
+ dwc_reg_write(hc_regs, DWC_HCTSIZ, hctsiz);
+
+ if (core_if->dma_enable)
+ dwc_reg_write(hc_regs, DWC_HCDMA, (u32) hc->xfer_buff);
+
+ /* Start the split */
+ if (hc->do_split) {
+ u32 hcsplt;
+
+ hcsplt = dwc_reg_read(hc_regs, DWC_HCSPLT);
+ hcsplt = DWC_HCSPLT_COMP_SPLT_RW(hcsplt, 1);
+ dwc_reg_write(hc_regs, DWC_HCSPLT, hcsplt);
+ }
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hcchar = DWC_HCCHAR_MULTI_CNT_RW(hcchar, hc->multi_count);
+ hc_set_even_odd_frame(core_if, hc, &hcchar);
+
+ /* Set host channel enable after all other setup is complete. */
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ hc->xfer_started = 1;
+ hc->requests++;
+ if (!core_if->dma_enable && !hc->ep_is_in && hc->xfer_len > 0)
+ /* Load OUT packet into the appropriate Tx FIFO. */
+ dwc_otg_hc_write_packet(core_if, hc);
+}
+
+/**
+ * This function continues a data transfer that was started by previous call
+ * to dwc_otg_hc_start_transfer</code>. The caller must ensure there is
+ * sufficient space in the request queue and Tx Data FIFO. This function
+ * should only be called in Slave mode. In DMA mode, the controller acts
+ * autonomously to complete transfers programmed to a host channel.
+ *
+ * For an OUT transfer, a new data packet is loaded into the appropriate FIFO
+ * if there is any data remaining to be queued. For an IN transfer, another
+ * data packet is always requested. For the SETUP phase of a control transfer,
+ * this function does nothing.
+ */
+static int dwc_otg_hc_continue_transfer(struct core_if *core_if,
+ struct dwc_hc *hc)
+{
+ if (hc->do_split) {
+ /* SPLITs always queue just once per channel */
+ return 0;
+ } else if (hc->data_pid_start == DWC_OTG_HC_PID_SETUP) {
+ /* SETUPs are queued only once since they can't be NAKed. */
+ return 0;
+ } else if (hc->ep_is_in) {
+ /*
+ * Always queue another request for other IN transfers. If
+ * back-to-back INs are issued and NAKs are received for both,
+ * the driver may still be processing the first NAK when the
+ * second NAK is received. When the interrupt handler clears
+ * the NAK interrupt for the first NAK, the second NAK will
+ * not be seen. So we can't depend on the NAK interrupt
+ * handler to requeue a NAKed request. Instead, IN requests
+ * are issued each time this function is called. When the
+ * transfer completes, the extra requests for the channel will
+ * be flushed.
+ */
+ u32 hcchar;
+ ulong hc_regs = core_if->host_if->hc_regs[hc->hc_num];
+
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hc_set_even_odd_frame(core_if, hc, &hcchar);
+
+ hcchar = DWC_HCCHAR_ENA_RW(hcchar, 1);
+ hcchar = DWC_HCCHAR_DIS_RW(hcchar, 0);
+ dwc_reg_write(hc_regs, DWC_HCCHAR, hcchar);
+
+ hc->requests++;
+ return 1;
+ } else {
+ /* OUT transfers. */
+ if (hc->xfer_count < hc->xfer_len) {
+ if (hc->ep_type == DWC_OTG_EP_TYPE_INTR ||
+ hc->ep_type == DWC_OTG_EP_TYPE_ISOC) {
+ u32 hcchar;
+ u32 hc_regs;
+
+ hc_regs =
+ core_if->host_if->hc_regs[hc->hc_num];
+ hcchar = dwc_reg_read(hc_regs, DWC_HCCHAR);
+ hc_set_even_odd_frame(core_if, hc, &hcchar);
+ }
+
+ /* Load OUT packet into the appropriate Tx FIFO. */
+ dwc_otg_hc_write_packet(core_if, hc);
+ hc->requests++;
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+}
+
+/**
+ * This function writes a packet into the Tx FIFO associated with the Host
+ * Channel. For a channel associated with a non-periodic EP, the non-periodic
+ * Tx FIFO is written. For a channel associated with a periodic EP, the
+ * periodic Tx FIFO is written. This function should only be called in Slave
+ * mode.
+ *
+ * Upon return the xfer_buff and xfer_count fields in hc are incremented by
+ * then number of bytes written to the Tx FIFO.
+ */
+
+/**
+ * Attempts to queue a single transaction request for a host channel
+ * associated with either a periodic or non-periodic transfer. This function
+ * assumes that there is space available in the appropriate request queue. For
+ * an OUT transfer or SETUP transaction in Slave mode, it checks whether space
+ * is available in the appropriate Tx FIFO.
+ */
+static int queue_transaction(struct dwc_hcd *hcd, struct dwc_hc *hc,
+ u16 _fifo_dwords_avail)
+{
+ int retval;
+
+ if (hcd->core_if->dma_enable) {
+ if (!hc->xfer_started) {
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ hc->qh->ping_state = 0;
+ }
+ retval = 0;
+ } else if (hc->halt_pending) {
+ /* Don't queue a request if the channel has been halted. */
+ retval = 0;
+ } else if (hc->halt_on_queue) {
+ dwc_otg_hc_halt(hcd->core_if, hc, hc->halt_status);
+ retval = 0;
+ } else if (hc->do_ping) {
+ if (!hc->xfer_started)
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ retval = 0;
+ } else if (!hc->ep_is_in || hc->data_pid_start ==
+ DWC_OTG_HC_PID_SETUP) {
+ if ((_fifo_dwords_avail * 4) >= hc->max_packet) {
+ if (!hc->xfer_started) {
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ retval = 1;
+ } else {
+ retval =
+ dwc_otg_hc_continue_transfer(hcd->core_if,
+ hc);
+ }
+ } else {
+ retval = -1;
+ }
+ } else {
+ if (!hc->xfer_started) {
+ dwc_otg_hc_start_transfer(hcd->core_if, hc);
+ retval = 1;
+ } else {
+ retval = dwc_otg_hc_continue_transfer(hcd->core_if, hc);
+ }
+ }
+ return retval;
+}
+
+/**
+ * Processes active non-periodic channels and queues transactions for these
+ * channels to the DWC_otg controller. After queueing transactions, the NP Tx
+ * FIFO Empty interrupt is enabled if there are more transactions to queue as
+ * NP Tx FIFO or request queue space becomes available. Otherwise, the NP Tx
+ * FIFO Empty interrupt is disabled.
+ */
+static void process_non_periodic_channels(struct dwc_hcd *hcd)
+{
+ u32 tx_status = 0;
+ struct list_head *orig_qh_ptr;
+ struct dwc_qh *qh;
+ int status;
+ int no_queue_space = 0;
+ int no_fifo_space = 0;
+ int more_to_do = 0;
+ ulong regs = hcd->core_if->core_global_regs;
+
+ /*
+ * Keep track of the starting point. Skip over the start-of-list
+ * entry.
+ */
+ if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active)
+ hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next;
+ orig_qh_ptr = hcd->non_periodic_qh_ptr;
+
+ /*
+ * Process once through the active list or until no more space is
+ * available in the request queue or the Tx FIFO.
+ */
+ do {
+ tx_status = dwc_reg_read(regs, DWC_GNPTXSTS);
+ if (!hcd->core_if->dma_enable &&
+ DWC_GNPTXSTS_NPTXQSPCAVAIL_RD(tx_status) == 0) {
+ no_queue_space = 1;
+ break;
+ }
+
+ qh = list_entry(hcd->non_periodic_qh_ptr, struct dwc_qh,
+ qh_list_entry);
+ status = queue_transaction(hcd, qh->channel,
+ DWC_GNPTXSTS_NPTXFSPCAVAIL_RD
+ (tx_status));
+
+ if (status > 0) {
+ more_to_do = 1;
+ } else if (status < 0) {
+ no_fifo_space = 1;
+ break;
+ }
+
+ /* Advance to next QH, skipping start-of-list entry. */
+ hcd->non_periodic_qh_ptr = hcd->non_periodic_qh_ptr->next;
+ if (hcd->non_periodic_qh_ptr == &hcd->non_periodic_sched_active)
+ hcd->non_periodic_qh_ptr =
+ hcd->non_periodic_qh_ptr->next;
+ } while (hcd->non_periodic_qh_ptr != orig_qh_ptr);
+
+ if (!hcd->core_if->dma_enable) {
+ u32 intr_mask = 0;
+
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ if (more_to_do || no_queue_space || no_fifo_space) {
+ /*
+ * May need to queue more transactions as the request
+ * queue or Tx FIFO empties. Enable the non-periodic
+ * Tx FIFO empty interrupt. (Always use the half-empty
+ * level to ensure that new requests are loaded as
+ * soon as possible.)
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, 0, intr_mask);
+ } else {
+ /*
+ * Disable the Tx FIFO empty interrupt since there are
+ * no more transactions that need to be queued right
+ * now. This function is called from interrupt
+ * handlers to queue more transactions as transfer
+ * states change.
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, intr_mask, 0);
+ }
+ }
+}
+
+/**
+ * Processes periodic channels for the next frame and queues transactions for
+ * these channels to the DWC_otg controller. After queueing transactions, the
+ * Periodic Tx FIFO Empty interrupt is enabled if there are more transactions
+ * to queue as Periodic Tx FIFO or request queue space becomes available.
+ * Otherwise, the Periodic Tx FIFO Empty interrupt is disabled.
+ */
+static void process_periodic_channels(struct dwc_hcd *hcd)
+{
+ u32 tx_status = 0;
+ struct list_head *qh_ptr;
+ struct dwc_qh *qh;
+ int status;
+ int no_queue_space = 0;
+ int no_fifo_space = 0;
+ ulong host_regs;
+
+ host_regs = hcd->core_if->host_if->host_global_regs;
+
+ qh_ptr = hcd->periodic_sched_assigned.next;
+ while (qh_ptr != &hcd->periodic_sched_assigned) {
+ tx_status = dwc_reg_read(host_regs, DWC_HPTXSTS);
+ if (DWC_HPTXSTS_PTXSPC_AVAIL_RD(tx_status) == 0) {
+ no_queue_space = 1;
+ break;
+ }
+
+ qh = list_entry(qh_ptr, struct dwc_qh, qh_list_entry);
+
+ /*
+ * Set a flag if we're queuing high-bandwidth in slave mode.
+ * The flag prevents any halts to get into the request queue in
+ * the middle of multiple high-bandwidth packets getting queued.
+ */
+ if (!hcd->core_if->dma_enable && qh->channel->multi_count > 1)
+ hcd->core_if->queuing_high_bandwidth = 1;
+
+ status = queue_transaction(hcd, qh->channel,
+ DWC_HPTXSTS_PTXFSPC_AVAIL_RD
+ (tx_status));
+ if (status < 0) {
+ no_fifo_space = 1;
+ break;
+ }
+
+ /*
+ * In Slave mode, stay on the current transfer until there is
+ * nothing more to do or the high-bandwidth request count is
+ * reached. In DMA mode, only need to queue one request. The
+ * controller automatically handles multiple packets for
+ * high-bandwidth transfers.
+ */
+ if (hcd->core_if->dma_enable || (status == 0 ||
+ qh->channel->requests ==
+ qh->channel->multi_count)) {
+ qh_ptr = qh_ptr->next;
+
+ /*
+ * Move the QH from the periodic assigned schedule to
+ * the periodic queued schedule.
+ */
+ list_move(&qh->qh_list_entry,
+ &hcd->periodic_sched_queued);
+
+ /* done queuing high bandwidth */
+ hcd->core_if->queuing_high_bandwidth = 0;
+ }
+ }
+
+ if (!hcd->core_if->dma_enable) {
+ u32 intr_mask = 0;
+
+ intr_mask |= DWC_INTMSK_NP_TXFIFO_EMPT;
+
+ if (!list_empty(&hcd->periodic_sched_assigned) ||
+ no_queue_space || no_fifo_space)
+ /*
+ * May need to queue more transactions as the request
+ * queue or Tx FIFO empties. Enable the periodic Tx
+ * FIFO empty interrupt. (Always use the half-empty
+ * level to ensure that new requests are loaded as
+ * soon as possible.)
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, 0, intr_mask);
+ else
+ /*
+ * Disable the Tx FIFO empty interrupt since there are
+ * no more transactions that need to be queued right
+ * now. This function is called from interrupt
+ * handlers to queue more transactions as transfer
+ * states change.
+ */
+ dwc_reg_modify(gintmsk_reg(hcd), 0, intr_mask, 0);
+ }
+}
+
+/**
+ * This function processes the currently active host channels and queues
+ * transactions for these channels to the DWC_otg controller. It is called
+ * from HCD interrupt handler functions.
+ */
+void dwc_otg_hcd_queue_transactions(struct dwc_hcd *hcd,
+ enum dwc_transaction_type tr_type)
+{
+ /* Process host channels associated with periodic transfers. */
+ if ((tr_type == DWC_OTG_TRANSACTION_PERIODIC ||
+ tr_type == DWC_OTG_TRANSACTION_ALL) &&
+ !list_empty(&hcd->periodic_sched_assigned))
+ process_periodic_channels(hcd);
+
+ /* Process host channels associated with non-periodic transfers. */
+ if (tr_type == DWC_OTG_TRANSACTION_NON_PERIODIC ||
+ tr_type == DWC_OTG_TRANSACTION_ALL) {
+ if (!list_empty(&hcd->non_periodic_sched_active)) {
+ process_non_periodic_channels(hcd);
+ } else {
+ /*
+ * Ensure NP Tx FIFO empty interrupt is disabled when
+ * there are no non-periodic transfers to process.
+ */
+ u32 gintmsk = 0;
+ gintmsk |= DWC_INTMSK_NP_TXFIFO_EMPT;
+ dwc_reg_modify(gintmsk_reg(hcd), 0, gintmsk, 0);
+ }
+ }
+}
+
+/**
+ * Sets the final status of an URB and returns it to the device driver. Any
+ * required cleanup of the URB is performed.
+ */
+void dwc_otg_hcd_complete_urb(struct dwc_hcd *hcd, struct urb *urb, int status)
+__releases(hcd->lock) __acquires(hcd->lock)
+{
+ urb->hcpriv = NULL;
+ usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb);
+
+ spin_unlock(&hcd->lock);
+ usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, status);
+ spin_lock(&hcd->lock);
+}
diff --git a/drivers/usb/dwc/hcd.h b/drivers/usb/dwc/hcd.h
new file mode 100644
index 0000000..c3d86e4
--- /dev/null
+++ b/drivers/usb/dwc/hcd.h
@@ -0,0 +1,416 @@
+/*
+ * DesignWare HS OTG controller driver
+ * Copyright (C) 2006 Synopsys, Inc.
+ * Portions Copyright (C) 2010 Applied Micro Circuits Corporation.
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License version 2 for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see http://www.gnu.org/licenses
+ * or write to the Free Software Foundation, Inc., 51 Franklin Street,
+ * Suite 500, Boston, MA 02110-1335 USA.
+ *
+ * Based on Synopsys driver version 2.60a
+ * Modified by Mark Miesfeld <mmiesfeld@apm.com>
+ * Modified by Stefan Roese <sr@denx.de>, DENX Software Engineering
+ * Modified by Chuck Meade <chuck@theptrgroup.com>
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL SYNOPSYS, INC. BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#if !defined(__DWC_HCD_H__)
+#define __DWC_HCD_H__
+
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#include "driver.h"
+
+/*
+ * This file contains the structures, constants, and interfaces for
+ * the Host Contoller Driver (HCD).
+ *
+ * The Host Controller Driver (HCD) is responsible for translating requests
+ * from the USB Driver into the appropriate actions on the DWC_otg controller.
+ * It isolates the USBD from the specifics of the controller by providing an
+ * API to the USBD.
+ */
+
+/* Phases for control transfers. */
+enum dwc_control_phase {
+ DWC_OTG_CONTROL_SETUP,
+ DWC_OTG_CONTROL_DATA,
+ DWC_OTG_CONTROL_STATUS
+};
+
+/* Transaction types. */
+enum dwc_transaction_type {
+ DWC_OTG_TRANSACTION_NONE,
+ DWC_OTG_TRANSACTION_PERIODIC,
+ DWC_OTG_TRANSACTION_NON_PERIODIC,
+ DWC_OTG_TRANSACTION_ALL
+};
+
+/*
+ * A Queue Transfer Descriptor (QTD) holds the state of a bulk, control,
+ * interrupt, or isochronous transfer. A single QTD is created for each URB
+ * (of one of these types) submitted to the HCD. The transfer associated with
+ * a QTD may require one or multiple transactions.
+ *
+ * A QTD is linked to a Queue Head, which is entered in either the
+ * non-periodic or periodic schedule for execution. When a QTD is chosen for
+ * execution, some or all of its transactions may be executed. After
+ * execution, the state of the QTD is updated. The QTD may be retired if all
+ * its transactions are complete or if an error occurred. Otherwise, it
+ * remains in the schedule so more transactions can be executed later.
+ */
+struct dwc_qtd {
+ /*
+ * Determines the PID of the next data packet for the data phase of
+ * control transfers. Ignored for other transfer types.
+ * One of the following values:
+ * - DWC_OTG_HC_PID_DATA0
+ * - DWC_OTG_HC_PID_DATA1
+ */
+ u8 data_toggle;
+
+ /* Current phase for control transfers (Setup, Data, or Status). */
+ enum dwc_control_phase control_phase;
+
+ /*
+ * Keep track of the current split type
+ * for FS/LS endpoints on a HS Hub
+ */
+ u8 complete_split;
+
+ /* How many bytes transferred during SSPLIT OUT */
+ u32 ssplit_out_xfer_count;
+
+ /*
+ * Holds the number of bus errors that have occurred for a transaction
+ * within this transfer.
+ */
+ u8 error_count;
+
+ /*
+ * Index of the next frame descriptor for an isochronous transfer. A
+ * frame descriptor describes the buffer position and length of the
+ * data to be transferred in the next scheduled (micro)frame of an
+ * isochronous transfer. It also holds status for that transaction.
+ * The frame index starts at 0.
+ */
+ int isoc_frame_index;
+
+ /* Position of the ISOC split on full/low speed */
+ u8 isoc_split_pos;
+
+ /* Position of the ISOC split in the buffer for the current frame */
+ u16 isoc_split_offset;
+
+ /* URB for this transfer */
+ struct urb *urb;
+
+ /* This list of QTDs */
+ struct list_head qtd_list_entry;
+
+ /* Field to track the qh pointer */
+ struct dwc_qh *qtd_qh_ptr;
+};
+
+/*
+ * A Queue Head (QH) holds the static characteristics of an endpoint and
+ * maintains a list of transfers (QTDs) for that endpoint. A QH structure may
+ * be entered in either the non-periodic or periodic schedule.
+ */
+struct dwc_qh {
+ /*
+ * Endpoint type.
+ * One of the following values:
+ * - USB_ENDPOINT_XFER_CONTROL
+ * - USB_ENDPOINT_XFER_ISOC
+ * - USB_ENDPOINT_XFER_BULK
+ * - USB_ENDPOINT_XFER_INT
+ */
+ u8 ep_type;
+ u8 ep_is_in;
+
+ /* wMaxPacketSize Field of Endpoint Descriptor. */
+ u16 maxp;
+
+ /*
+ * Determines the PID of the next data packet for non-control
+ * transfers. Ignored for control transfers.
+ * One of the following values:
+ * - DWC_OTG_HC_PID_DATA0
+ * - DWC_OTG_HC_PID_DATA1
+ */
+ u8 data_toggle;
+
+ /* Ping state if 1. */
+ u8 ping_state;
+
+ /* List of QTDs for this QH. */
+ struct list_head qtd_list;
+
+ /* Host channel currently processing transfers for this QH. */
+ struct dwc_hc *channel;
+
+ /* QTD currently assigned to a host channel for this QH. */
+ struct dwc_qtd *qtd_in_process;
+
+ /* Full/low speed endpoint on high-speed hub requires split. */
+ u8 do_split;
+
+ /* Periodic schedule information */
+
+ /* Bandwidth in microseconds per (micro)frame. */
+ u8 usecs;
+
+ /* Interval between transfers in (micro)frames. */
+ u16 interval;
+
+ /*
+ * (micro)frame to initialize a periodic transfer. The transfer
+ * executes in the following (micro)frame.
+ */
+ u16 sched_frame;
+
+ /* (micro)frame at which last start split was initialized. */
+ u16 start_split_frame;
+
+ u16 speed;
+ u16 frame_usecs[8];
+
+ /* Entry for QH in either the periodic or non-periodic schedule. */
+ struct list_head qh_list_entry;
+};
+
+/* Gets the struct usb_hcd that contains a struct dwc_hcd. */
+static inline struct usb_hcd *dwc_otg_hcd_to_hcd(struct dwc_hcd *dwc_hcd)
+{
+ return container_of((void *)dwc_hcd, struct usb_hcd, hcd_priv);
+}
+
+/* HCD Create/Destroy Functions */
+extern int __init dwc_otg_hcd_init(struct device *_dev,
+ struct dwc_otg_device *dwc_dev);
+extern void dwc_otg_hcd_remove(struct device *_dev);
+
+/*
+ * The following functions support managing the DWC_otg controller in host
+ * mode.
+ */
+extern int dwc_otg_hcd_get_frame_number(struct usb_hcd *hcd);
+extern void dwc_otg_hc_cleanup(struct core_if *core_if, struct dwc_hc *hc);
+extern void dwc_otg_hc_halt(struct core_if *core_if, struct dwc_hc *hc,
+ enum dwc_halt_status _halt_status);
+
+/* Transaction Execution Functions */
+extern enum dwc_transaction_type dwc_otg_hcd_select_transactions(struct dwc_hcd
+ *hcd);
+extern void dwc_otg_hcd_queue_transactions(struct dwc_hcd *hcd,
+ enum dwc_transaction_type tr_type);
+extern void dwc_otg_hcd_complete_urb(struct dwc_hcd *_hcd, struct urb *urb,
+ int status);
+
+/* Interrupt Handler Functions */
+extern int dwc_otg_hcd_handle_intr(struct dwc_hcd *hcd);
+
+/* Schedule Queue Functions */
+extern int init_hcd_usecs(struct dwc_hcd *hcd);
+extern void dwc_otg_hcd_qh_free(struct dwc_qh *qh);
+extern void dwc_otg_hcd_qh_remove(struct dwc_hcd *hcd, struct dwc_qh *qh);
+extern void dwc_otg_hcd_qh_deactivate(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ int sched_csplit);
+extern int dwc_otg_hcd_qh_deferr(struct dwc_hcd *hcd, struct dwc_qh *qh,
+ int delay);
+extern struct dwc_qtd *dwc_otg_hcd_qtd_create(struct urb *urb,
+ gfp_t _mem_flags);
+extern int dwc_otg_hcd_qtd_add(struct dwc_qtd *qtd, struct dwc_hcd *dwc_hcd);
+
+/*
+ * Frees the memory for a QTD structure. QTD should already be removed from
+ * list.
+ */
+static inline void dwc_otg_hcd_qtd_free(struct dwc_qtd *_qtd)
+{
+ kfree(_qtd);
+}
+
+/* Removes a QTD from list. */
+static inline void dwc_otg_hcd_qtd_remove(struct dwc_qtd *_qtd)
+{
+ list_del(&_qtd->qtd_list_entry);
+}
+
+/* Remove and free a QTD */
+static inline void dwc_otg_hcd_qtd_remove_and_free(struct dwc_qtd *_qtd)
+{
+ dwc_otg_hcd_qtd_remove(_qtd);
+ dwc_otg_hcd_qtd_free(_qtd);
+}
+
+struct dwc_qh *dwc_urb_to_qh(struct urb *_urb);
+
+/* Gets the usb_host_endpoint associated with an URB. */
+static inline struct usb_host_endpoint *dwc_urb_to_endpoint(struct urb *_urb)
+{
+ struct usb_device *dev = _urb->dev;
+ int ep_num = usb_pipeendpoint(_urb->pipe);
+
+ if (usb_pipein(_urb->pipe))
+ return dev->ep_in[ep_num];
+ else
+ return dev->ep_out[ep_num];
+}
+
+/*
+ * Gets the endpoint number from a _bEndpointAddress argument. The endpoint is
+ * qualified with its direction (possible 32 endpoints per device).
+ */
+#define dwc_ep_addr_to_endpoint(_bEndpointAddress_) \
+ ((_bEndpointAddress_ & USB_ENDPOINT_NUMBER_MASK) | \
+ ((_bEndpointAddress_ & USB_DIR_IN) != 0) << 4)
+
+/* Gets the QH that contains the list_head */
+#define dwc_list_to_qh(_list_head_ptr_) \
+ (container_of(_list_head_ptr_, struct dwc_qh, qh_list_entry))
+
+/* Gets the QTD that contains the list_head */
+#define dwc_list_to_qtd(_list_head_ptr_) \
+ (container_of(_list_head_ptr_, struct dwc_qtd, qtd_list_entry))
+
+/* Check if QH is non-periodic */
+#define dwc_qh_is_non_per(_qh_ptr_) \
+ ((_qh_ptr_->ep_type == USB_ENDPOINT_XFER_BULK) || \
+ (_qh_ptr_->ep_type == USB_ENDPOINT_XFER_CONTROL))
+
+/* High bandwidth multiplier as encoded in highspeed endpoint descriptors */
+#define dwc_hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
+
+/* Packet size for any kind of endpoint descriptor */
+#define dwc_max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff)
+
+/*
+ * Returns true if _frame1 is less than or equal to _frame2. The comparison is
+ * done modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the
+ * frame number when the max frame number is reached.
+ */
+static inline int dwc_frame_num_le(u16 _frame1, u16 _frame2)
+{
+ return ((_frame2 - _frame1) & DWC_HFNUM_MAX_FRNUM) <=
+ (DWC_HFNUM_MAX_FRNUM >> 1);
+}
+
+/*
+ * Returns true if _frame1 is greater than _frame2. The comparison is done
+ * modulo DWC_HFNUM_MAX_FRNUM. This accounts for the rollover of the frame
+ * number when the max frame number is reached.
+ */
+static inline int dwc_frame_num_gt(u16 _frame1, u16 _frame2)
+{
+ return (_frame1 != _frame2) &&
+ (((_frame1 - _frame2) &
+ DWC_HFNUM_MAX_FRNUM) < (DWC_HFNUM_MAX_FRNUM >> 1));
+}
+
+/*
+ * Increments _frame by the amount specified by _inc. The addition is done
+ * modulo DWC_HFNUM_MAX_FRNUM. Returns the incremented value.
+ */
+static inline u16 dwc_frame_num_inc(u16 _frame, u16 _inc)
+{
+ return (_frame + _inc) & DWC_HFNUM_MAX_FRNUM;
+}
+
+static inline u16 dwc_full_frame_num(u16 _frame)
+{
+ return ((_frame) & DWC_HFNUM_MAX_FRNUM) >> 3;
+}
+
+static inline u16 dwc_micro_frame_num(u16 _frame)
+{
+ return (_frame) & 0x7;
+}
+
+static inline ulong gintsts_reg(struct dwc_hcd *hcd)
+{
+ ulong global_regs = hcd->core_if->core_global_regs;
+ return global_regs + DWC_GINTSTS;
+}
+
+static inline ulong gintmsk_reg(struct dwc_hcd *hcd)
+{
+ ulong global_regs = hcd->core_if->core_global_regs;
+ return global_regs + DWC_GINTMSK;
+}
+
+static inline ulong gahbcfg_reg(struct dwc_hcd *hcd)
+{
+ ulong global_regs = hcd->core_if->core_global_regs;
+ return global_regs + DWC_GAHBCFG;
+}
+
+static inline const char *pipetype_str(unsigned int pipe)
+{
+ switch (usb_pipetype(pipe)) {
+ case PIPE_CONTROL:
+ return "control";
+ case PIPE_BULK:
+ return "bulk";
+ case PIPE_INTERRUPT:
+ return "interrupt";
+ case PIPE_ISOCHRONOUS:
+ return "isochronous";
+ default:
+ return "unknown";
+ }
+}
+
+static inline const char *dev_speed_str(enum usb_device_speed speed)
+{
+ switch (speed) {
+ case USB_SPEED_HIGH:
+ return "high";
+ case USB_SPEED_FULL:
+ return "full";
+ case USB_SPEED_LOW:
+ return "low";
+ default:
+ return "unknown";
+ }
+}
+
+static inline const char *ep_type_str(u8 type)
+{
+ switch (type) {
+ case USB_ENDPOINT_XFER_ISOC:
+ return "isochronous";
+ case USB_ENDPOINT_XFER_INT:
+ return "interrupt";
+ case USB_ENDPOINT_XFER_CONTROL:
+ return "control";
+ case USB_ENDPOINT_XFER_BULK:
+ return "bulk";
+ default:
+ return "?";
+ }
+}
+#endif
--
1.7.0.4
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox