* [PATCH 3/5] powerpc: Unify mem= handling
From: Michael Ellerman @ 2006-05-17 8:00 UTC (permalink / raw)
To: Paul Mackerras; +Cc: linuxppc-dev, Kumar Gala
In-Reply-To: <1147852841.148164.91320074069.qpush@concordia>
We currently do mem= handling in three seperate places. And as benh pointed out
I wrote two of them. Now that we parse command line parameters earlier we can
clean this mess up.
Moving the parsing out of prom_init means the device tree might be allocated
above the memory limit. If that happens we'd have to move it. As it happens
we already have logic to do that for kdump, so just genericise it.
This also means we might have reserved regions above the memory limit, if we
do the bootmem allocator will blow up, so we have to modify
lmb_enforce_memory_limit() to truncate the reserves as well.
Tested on P5 LPAR, iSeries, F50, 44p. Tested moving device tree on P5 and
44p and F50.
Signed-off-by: Michael Ellerman <michael@ellerman.id.au>
---
arch/powerpc/kernel/machine_kexec_64.c | 5 +
arch/powerpc/kernel/prom.c | 89 +++++++++++++++++----------------
arch/powerpc/kernel/prom_init.c | 55 +-------------------
arch/powerpc/kernel/setup_64.c | 3 -
arch/powerpc/mm/lmb.c | 43 +++++++++++----
arch/powerpc/platforms/iseries/setup.c | 22 --------
include/asm-powerpc/kexec.h | 13 ++++
7 files changed, 100 insertions(+), 130 deletions(-)
Index: to-merge/arch/powerpc/kernel/machine_kexec_64.c
===================================================================
--- to-merge.orig/arch/powerpc/kernel/machine_kexec_64.c
+++ to-merge/arch/powerpc/kernel/machine_kexec_64.c
@@ -339,3 +339,8 @@ void __init kexec_setup(void)
{
export_htab_values();
}
+
+int overlaps_crashkernel(unsigned long start, unsigned long size)
+{
+ return (start + size) > crashk_res.start && start <= crashk_res.end;
+}
Index: to-merge/arch/powerpc/kernel/prom.c
===================================================================
--- to-merge.orig/arch/powerpc/kernel/prom.c
+++ to-merge/arch/powerpc/kernel/prom.c
@@ -50,6 +50,7 @@
#include <asm/machdep.h>
#include <asm/pSeries_reconfig.h>
#include <asm/pci-bridge.h>
+#include <asm/kexec.h>
#ifdef DEBUG
#define DBG(fmt...) printk(KERN_ERR fmt)
@@ -836,6 +837,42 @@ static unsigned long __init unflatten_dt
return mem;
}
+static int __init early_parse_mem(char *p)
+{
+ if (!p)
+ return 1;
+
+ memory_limit = PAGE_ALIGN(memparse(p, &p));
+ DBG("memory limit = 0x%lx\n", memory_limit);
+
+ return 0;
+}
+early_param("mem", early_parse_mem);
+
+/*
+ * The device tree may be allocated below our memory limit, or inside the
+ * crash kernel region for kdump. If so, move it out now.
+ */
+static void move_device_tree(void)
+{
+ unsigned long start, size;
+ void *p;
+
+ DBG("-> move_device_tree\n");
+
+ start = __pa(initial_boot_params);
+ size = initial_boot_params->totalsize;
+
+ if ((memory_limit && (start + size) > memory_limit) ||
+ overlaps_crashkernel(start, size)) {
+ p = __va(lmb_alloc_base(size, PAGE_SIZE, lmb.rmo_size));
+ memcpy(p, initial_boot_params, size);
+ initial_boot_params = (struct boot_param_header *)p;
+ DBG("Moved device tree to 0x%p\n", p);
+ }
+
+ DBG("<- move_device_tree\n");
+}
/**
* unflattens the device-tree passed by the firmware, creating the
@@ -1070,6 +1107,7 @@ static int __init early_init_dt_scan_cho
iommu_force_on = 1;
#endif
+ /* mem=x on the command line is the preferred mechanism */
lprop = of_get_flat_dt_prop(node, "linux,memory-limit", NULL);
if (lprop)
memory_limit = *lprop;
@@ -1123,17 +1161,6 @@ static int __init early_init_dt_scan_cho
DBG("Command line is: %s\n", cmd_line);
- if (strstr(cmd_line, "mem=")) {
- char *p, *q;
-
- for (q = cmd_line; (p = strstr(q, "mem=")) != 0; ) {
- q = p + 4;
- if (p > cmd_line && p[-1] != ' ')
- continue;
- memory_limit = memparse(q, &q);
- }
- }
-
/* break now */
return 1;
}
@@ -1297,11 +1324,6 @@ void __init early_init_devtree(void *par
strlcpy(saved_command_line, cmd_line, COMMAND_LINE_SIZE);
parse_early_param();
- lmb_enforce_memory_limit(memory_limit);
- lmb_analyze();
-
- DBG("Phys. mem: %lx\n", lmb_phys_mem_size());
-
/* Reserve LMB regions used by kernel, initrd, dt, etc... */
lmb_reserve(PHYSICAL_START, __pa(klimit) - PHYSICAL_START);
#ifdef CONFIG_CRASH_DUMP
@@ -1309,6 +1331,15 @@ void __init early_init_devtree(void *par
#endif
early_reserve_mem();
+ lmb_enforce_memory_limit(memory_limit);
+ lmb_analyze();
+
+ DBG("Phys. mem: %lx\n", lmb_phys_mem_size());
+
+ /* We may need to relocate the flat tree, do it now.
+ * FIXME .. and the initrd too? */
+ move_device_tree();
+
DBG("Scanning CPUs ...\n");
/* Retreive CPU related informations from the flat tree
@@ -2058,29 +2089,3 @@ int prom_update_property(struct device_n
return 0;
}
-#ifdef CONFIG_KEXEC
-/* We may have allocated the flat device tree inside the crash kernel region
- * in prom_init. If so we need to move it out into regular memory. */
-void kdump_move_device_tree(void)
-{
- unsigned long start, end;
- struct boot_param_header *new;
-
- start = __pa((unsigned long)initial_boot_params);
- end = start + initial_boot_params->totalsize;
-
- if (end < crashk_res.start || start > crashk_res.end)
- return;
-
- new = (struct boot_param_header*)
- __va(lmb_alloc(initial_boot_params->totalsize, PAGE_SIZE));
-
- memcpy(new, initial_boot_params, initial_boot_params->totalsize);
-
- initial_boot_params = new;
-
- DBG("Flat device tree blob moved to %p\n", initial_boot_params);
-
- /* XXX should we unreserve the old DT? */
-}
-#endif /* CONFIG_KEXEC */
Index: to-merge/arch/powerpc/kernel/prom_init.c
===================================================================
--- to-merge.orig/arch/powerpc/kernel/prom_init.c
+++ to-merge/arch/powerpc/kernel/prom_init.c
@@ -194,8 +194,6 @@ static int __initdata of_platform;
static char __initdata prom_cmd_line[COMMAND_LINE_SIZE];
-static unsigned long __initdata prom_memory_limit;
-
static unsigned long __initdata alloc_top;
static unsigned long __initdata alloc_top_high;
static unsigned long __initdata alloc_bottom;
@@ -594,16 +592,6 @@ static void __init early_cmdline_parse(v
}
#endif
- opt = strstr(RELOC(prom_cmd_line), RELOC("mem="));
- if (opt) {
- opt += 4;
- RELOC(prom_memory_limit) = prom_memparse(opt, (const char **)&opt);
-#ifdef CONFIG_PPC64
- /* Align to 16 MB == size of ppc64 large page */
- RELOC(prom_memory_limit) = ALIGN(RELOC(prom_memory_limit), 0x1000000);
-#endif
- }
-
#ifdef CONFIG_KEXEC
/*
* crashkernel=size@addr specifies the location to reserve for
@@ -1115,29 +1103,6 @@ static void __init prom_init_mem(void)
}
/*
- * If prom_memory_limit is set we reduce the upper limits *except* for
- * alloc_top_high. This must be the real top of RAM so we can put
- * TCE's up there.
- */
-
- RELOC(alloc_top_high) = RELOC(ram_top);
-
- if (RELOC(prom_memory_limit)) {
- if (RELOC(prom_memory_limit) <= RELOC(alloc_bottom)) {
- prom_printf("Ignoring mem=%x <= alloc_bottom.\n",
- RELOC(prom_memory_limit));
- RELOC(prom_memory_limit) = 0;
- } else if (RELOC(prom_memory_limit) >= RELOC(ram_top)) {
- prom_printf("Ignoring mem=%x >= ram_top.\n",
- RELOC(prom_memory_limit));
- RELOC(prom_memory_limit) = 0;
- } else {
- RELOC(ram_top) = RELOC(prom_memory_limit);
- RELOC(rmo_top) = min(RELOC(rmo_top), RELOC(prom_memory_limit));
- }
- }
-
- /*
* Setup our top alloc point, that is top of RMO or top of
* segment 0 when running non-LPAR.
* Some RS64 machines have buggy firmware where claims up at
@@ -1149,9 +1114,9 @@ static void __init prom_init_mem(void)
RELOC(rmo_top) = RELOC(ram_top);
RELOC(rmo_top) = min(0x30000000ul, RELOC(rmo_top));
RELOC(alloc_top) = RELOC(rmo_top);
+ RELOC(alloc_top_high) = RELOC(ram_top);
prom_printf("memory layout at init:\n");
- prom_printf(" memory_limit : %x (16 MB aligned)\n", RELOC(prom_memory_limit));
prom_printf(" alloc_bottom : %x\n", RELOC(alloc_bottom));
prom_printf(" alloc_top : %x\n", RELOC(alloc_top));
prom_printf(" alloc_top_hi : %x\n", RELOC(alloc_top_high));
@@ -1348,16 +1313,10 @@ static void __init prom_initialize_tce_t
reserve_mem(local_alloc_bottom, local_alloc_top - local_alloc_bottom);
- if (RELOC(prom_memory_limit)) {
- /*
- * We align the start to a 16MB boundary so we can map
- * the TCE area using large pages if possible.
- * The end should be the top of RAM so no need to align it.
- */
- RELOC(prom_tce_alloc_start) = _ALIGN_DOWN(local_alloc_bottom,
- 0x1000000);
- RELOC(prom_tce_alloc_end) = local_alloc_top;
- }
+ /* These are only really needed if there is a memory limit in
+ * effect, but we don't know so export them always. */
+ RELOC(prom_tce_alloc_start) = local_alloc_bottom;
+ RELOC(prom_tce_alloc_end) = local_alloc_top;
/* Flag the first invalid entry */
prom_debug("ending prom_initialize_tce_table\n");
@@ -2265,10 +2224,6 @@ unsigned long __init prom_init(unsigned
/*
* Fill in some infos for use by the kernel later on
*/
- if (RELOC(prom_memory_limit))
- prom_setprop(_prom->chosen, "/chosen", "linux,memory-limit",
- &RELOC(prom_memory_limit),
- sizeof(prom_memory_limit));
#ifdef CONFIG_PPC64
if (RELOC(ppc64_iommu_off))
prom_setprop(_prom->chosen, "/chosen", "linux,iommu-off",
Index: to-merge/arch/powerpc/kernel/setup_64.c
===================================================================
--- to-merge.orig/arch/powerpc/kernel/setup_64.c
+++ to-merge/arch/powerpc/kernel/setup_64.c
@@ -353,9 +353,6 @@ void __init setup_system(void)
{
DBG(" -> setup_system()\n");
-#ifdef CONFIG_KEXEC
- kdump_move_device_tree();
-#endif
/*
* Unflatten the device-tree passed by prom_init or kexec
*/
Index: to-merge/arch/powerpc/mm/lmb.c
===================================================================
--- to-merge.orig/arch/powerpc/mm/lmb.c
+++ to-merge/arch/powerpc/mm/lmb.c
@@ -89,20 +89,25 @@ static long __init lmb_regions_adjacent(
return lmb_addrs_adjacent(base1, size1, base2, size2);
}
-/* Assumption: base addr of region 1 < base addr of region 2 */
-static void __init lmb_coalesce_regions(struct lmb_region *rgn,
- unsigned long r1, unsigned long r2)
+static void __init lmb_remove_region(struct lmb_region *rgn, unsigned long r)
{
unsigned long i;
- rgn->region[r1].size += rgn->region[r2].size;
- for (i=r2; i < rgn->cnt-1; i++) {
- rgn->region[i].base = rgn->region[i+1].base;
- rgn->region[i].size = rgn->region[i+1].size;
+ for (i = r; i < rgn->cnt - 1; i++) {
+ rgn->region[i].base = rgn->region[i + 1].base;
+ rgn->region[i].size = rgn->region[i + 1].size;
}
rgn->cnt--;
}
+/* Assumption: base addr of region 1 < base addr of region 2 */
+static void __init lmb_coalesce_regions(struct lmb_region *rgn,
+ unsigned long r1, unsigned long r2)
+{
+ rgn->region[r1].size += rgn->region[r2].size;
+ lmb_remove_region(rgn, r2);
+}
+
/* This routine called with relocation disabled. */
void __init lmb_init(void)
{
@@ -294,17 +299,16 @@ unsigned long __init lmb_end_of_DRAM(voi
return (lmb.memory.region[idx].base + lmb.memory.region[idx].size);
}
-/*
- * Truncate the lmb list to memory_limit if it's set
- * You must call lmb_analyze() after this.
- */
+/* You must call lmb_analyze() after this. */
void __init lmb_enforce_memory_limit(unsigned long memory_limit)
{
unsigned long i, limit;
+ struct lmb_property *p;
if (! memory_limit)
return;
+ /* Truncate the lmb regions to satisfy the memory limit. */
limit = memory_limit;
for (i = 0; i < lmb.memory.cnt; i++) {
if (limit > lmb.memory.region[i].size) {
@@ -316,4 +320,21 @@ void __init lmb_enforce_memory_limit(uns
lmb.memory.cnt = i + 1;
break;
}
+
+ lmb.rmo_size = lmb.memory.region[0].size;
+
+ /* And truncate any reserves above the limit also. */
+ for (i = 0; i < lmb.reserved.cnt; i++) {
+ p = &lmb.reserved.region[i];
+
+ if (p->base > memory_limit)
+ p->size = 0;
+ else if ((p->base + p->size) > memory_limit)
+ p->size = memory_limit - p->base;
+
+ if (p->size == 0) {
+ lmb_remove_region(&lmb.reserved, i);
+ i--;
+ }
+ }
}
Index: to-merge/arch/powerpc/platforms/iseries/setup.c
===================================================================
--- to-merge.orig/arch/powerpc/platforms/iseries/setup.c
+++ to-merge/arch/powerpc/platforms/iseries/setup.c
@@ -90,8 +90,6 @@ extern unsigned long embedded_sysmap_end
extern unsigned long iSeries_recal_tb;
extern unsigned long iSeries_recal_titan;
-static unsigned long cmd_mem_limit;
-
struct MemoryBlock {
unsigned long absStart;
unsigned long absEnd;
@@ -1023,8 +1021,6 @@ void build_flat_dt(struct iseries_flat_d
/* /chosen */
dt_start_node(dt, "chosen");
dt_prop_str(dt, "bootargs", cmd_line);
- if (cmd_mem_limit)
- dt_prop_u64(dt, "linux,memory-limit", cmd_mem_limit);
dt_end_node(dt);
dt_cpus(dt);
@@ -1050,29 +1046,11 @@ void * __init iSeries_early_setup(void)
iSeries_get_cmdline();
- /* Save unparsed command line copy for /proc/cmdline */
- strlcpy(saved_command_line, cmd_line, COMMAND_LINE_SIZE);
-
- /* Parse early parameters, in particular mem=x */
- parse_early_param();
-
build_flat_dt(&iseries_dt, phys_mem_size);
return (void *) __pa(&iseries_dt);
}
-/*
- * On iSeries we just parse the mem=X option from the command line.
- * On pSeries it's a bit more complicated, see prom_init_mem()
- */
-static int __init early_parsemem(char *p)
-{
- if (p)
- cmd_mem_limit = ALIGN(memparse(p, &p), PAGE_SIZE);
- return 0;
-}
-early_param("mem", early_parsemem);
-
static void hvputc(char c)
{
if (c == '\n')
Index: to-merge/include/asm-powerpc/kexec.h
===================================================================
--- to-merge.orig/include/asm-powerpc/kexec.h
+++ to-merge/include/asm-powerpc/kexec.h
@@ -31,9 +31,10 @@
#define KEXEC_ARCH KEXEC_ARCH_PPC
#endif
+#ifndef __ASSEMBLY__
+
#ifdef CONFIG_KEXEC
-#ifndef __ASSEMBLY__
#ifdef __powerpc64__
/*
* This function is responsible for capturing register states if coming
@@ -123,8 +124,16 @@ extern int default_machine_kexec_prepare
extern void default_machine_crash_shutdown(struct pt_regs *regs);
extern void machine_kexec_simple(struct kimage *image);
+extern int overlaps_crashkernel(unsigned long start, unsigned long size);
+
+#else /* !CONFIG_KEXEC */
+
+static inline int overlaps_crashkernel(unsigned long start, unsigned long size)
+{
+ return 0;
+}
-#endif /* ! __ASSEMBLY__ */
#endif /* CONFIG_KEXEC */
+#endif /* ! __ASSEMBLY__ */
#endif /* __KERNEL__ */
#endif /* _ASM_POWERPC_KEXEC_H */
^ permalink raw reply
* [PATCH 4/5] powerpc: Kdump header cleanup
From: Michael Ellerman @ 2006-05-17 8:00 UTC (permalink / raw)
To: Paul Mackerras; +Cc: linuxppc-dev, Kumar Gala
In-Reply-To: <1147852841.148164.91320074069.qpush@concordia>
We need to know the base address of the kdump kernel even when we're not a
kdump kernel, so add a #define for it. Move the logic that sets the kdump
kernelbase into kdump.h instead of page.h.
Rename kdump_setup() to setup_kdump_trampoline() to make it clearer what it's
doing, and add an empty definition for the !CRASH_DUMP case to avoid a
#define in the C code. Similarly, add reserve_kdump_trampoline().
Signed-off-by: Michael Ellerman <michael@ellerman.id.au>
---
arch/powerpc/kernel/crash_dump.c | 11 ++++++++---
arch/powerpc/kernel/prom.c | 4 +---
arch/powerpc/kernel/setup_64.c | 4 +---
include/asm-powerpc/kdump.h | 29 +++++++++++++++++++++++++++--
include/asm-powerpc/page.h | 8 +-------
5 files changed, 38 insertions(+), 18 deletions(-)
Index: to-merge/arch/powerpc/kernel/crash_dump.c
===================================================================
--- to-merge.orig/arch/powerpc/kernel/crash_dump.c
+++ to-merge/arch/powerpc/kernel/crash_dump.c
@@ -25,6 +25,11 @@
#define DBG(fmt...)
#endif
+void reserve_kdump_trampoline(void)
+{
+ lmb_reserve(0, KDUMP_RESERVE_LIMIT);
+}
+
static void __init create_trampoline(unsigned long addr)
{
/* The maximum range of a single instruction branch, is the current
@@ -39,11 +44,11 @@ static void __init create_trampoline(uns
create_branch(addr + 4, addr + PHYSICAL_START, 0);
}
-void __init kdump_setup(void)
+void __init setup_kdump_trampoline(void)
{
unsigned long i;
- DBG(" -> kdump_setup()\n");
+ DBG(" -> setup_kdump_trampoline()\n");
for (i = KDUMP_TRAMPOLINE_START; i < KDUMP_TRAMPOLINE_END; i += 8) {
create_trampoline(i);
@@ -52,7 +57,7 @@ void __init kdump_setup(void)
create_trampoline(__pa(system_reset_fwnmi) - PHYSICAL_START);
create_trampoline(__pa(machine_check_fwnmi) - PHYSICAL_START);
- DBG(" <- kdump_setup()\n");
+ DBG(" <- setup_kdump_trampoline()\n");
}
#ifdef CONFIG_PROC_VMCORE
Index: to-merge/arch/powerpc/kernel/prom.c
===================================================================
--- to-merge.orig/arch/powerpc/kernel/prom.c
+++ to-merge/arch/powerpc/kernel/prom.c
@@ -1326,9 +1326,7 @@ void __init early_init_devtree(void *par
/* Reserve LMB regions used by kernel, initrd, dt, etc... */
lmb_reserve(PHYSICAL_START, __pa(klimit) - PHYSICAL_START);
-#ifdef CONFIG_CRASH_DUMP
- lmb_reserve(0, KDUMP_RESERVE_LIMIT);
-#endif
+ reserve_kdump_trampoline();
early_reserve_mem();
lmb_enforce_memory_limit(memory_limit);
Index: to-merge/arch/powerpc/kernel/setup_64.c
===================================================================
--- to-merge.orig/arch/powerpc/kernel/setup_64.c
+++ to-merge/arch/powerpc/kernel/setup_64.c
@@ -199,9 +199,7 @@ void __init early_setup(unsigned long dt
/* Probe the machine type */
probe_machine();
-#ifdef CONFIG_CRASH_DUMP
- kdump_setup();
-#endif
+ setup_kdump_trampoline();
DBG("Found, Initializing memory management...\n");
Index: to-merge/include/asm-powerpc/kdump.h
===================================================================
--- to-merge.orig/include/asm-powerpc/kdump.h
+++ to-merge/include/asm-powerpc/kdump.h
@@ -1,13 +1,38 @@
#ifndef _PPC64_KDUMP_H
#define _PPC64_KDUMP_H
+/* Kdump kernel runs at 32 MB, change at your peril. */
+#define KDUMP_KERNELBASE 0x2000000
+
/* How many bytes to reserve at zero for kdump. The reserve limit should
- * be greater or equal to the trampoline's end address. */
+ * be greater or equal to the trampoline's end address.
+ * Reserve to the end of the FWNMI area, see head_64.S */
#define KDUMP_RESERVE_LIMIT 0x8000
+#ifdef CONFIG_CRASH_DUMP
+
+#define PHYSICAL_START KDUMP_KERNELBASE
#define KDUMP_TRAMPOLINE_START 0x0100
#define KDUMP_TRAMPOLINE_END 0x3000
-extern void kdump_setup(void);
+#else /* !CONFIG_CRASH_DUMP */
+
+#define PHYSICAL_START 0x0
+
+#endif /* CONFIG_CRASH_DUMP */
+
+#ifndef __ASSEMBLY__
+#ifdef CONFIG_CRASH_DUMP
+
+extern void reserve_kdump_trampoline(void);
+extern void setup_kdump_trampoline(void);
+
+#else /* !CONFIG_CRASH_DUMP */
+
+static inline void reserve_kdump_trampoline(void) { ; }
+static inline void setup_kdump_trampoline(void) { ; }
+
+#endif /* CONFIG_CRASH_DUMP */
+#endif /* __ASSEMBLY__ */
#endif /* __PPC64_KDUMP_H */
Index: to-merge/include/asm-powerpc/page.h
===================================================================
--- to-merge.orig/include/asm-powerpc/page.h
+++ to-merge/include/asm-powerpc/page.h
@@ -13,6 +13,7 @@
#ifdef __KERNEL__
#include <linux/config.h>
#include <asm/asm-compat.h>
+#include <asm/kdump.h>
/*
* On PPC32 page size is 4K. For PPC64 we support either 4K or 64K software
@@ -52,13 +53,6 @@
* If you want to test if something's a kernel address, use is_kernel_addr().
*/
-#ifdef CONFIG_CRASH_DUMP
-/* Kdump kernel runs at 32 MB, change at your peril. */
-#define PHYSICAL_START 0x2000000
-#else
-#define PHYSICAL_START 0x0
-#endif
-
#define PAGE_OFFSET ASM_CONST(CONFIG_KERNEL_START)
#define KERNELBASE (PAGE_OFFSET + PHYSICAL_START)
^ permalink raw reply
* [PATCH 5/5] powerpc: Move crashkernel= handling into the kernel.
From: Michael Ellerman @ 2006-05-17 8:00 UTC (permalink / raw)
To: Paul Mackerras; +Cc: linuxppc-dev, Kumar Gala
In-Reply-To: <1147852841.148164.91320074069.qpush@concordia>
Currently we parse crashkernel= in prom_init.c, which means for other boot
loaders to support Kdump they also need to parse the command line and setup
the appropriate crash kernel properties.
With early param parsing done earlier we can do crashkernel= parsing in the
early kernel code and avoid the need for every bootloader to do it for us.
We still support the device tree properties if they're specified by firmware,
however the command line overrides anything we find there.
Tested on P5 LPAR.
Signed-off-by: Michael Ellerman <michael@ellerman.id.au>
---
arch/powerpc/kernel/machine_kexec_64.c | 94 +++++++++++++++++++++++++++++++++
arch/powerpc/kernel/prom.c | 1
arch/powerpc/kernel/prom_init.c | 54 ------------------
include/asm-powerpc/kexec.h | 2
4 files changed, 97 insertions(+), 54 deletions(-)
Index: to-merge/arch/powerpc/kernel/machine_kexec_64.c
===================================================================
--- to-merge.orig/arch/powerpc/kernel/machine_kexec_64.c
+++ to-merge/arch/powerpc/kernel/machine_kexec_64.c
@@ -21,6 +21,7 @@
#include <asm/machdep.h>
#include <asm/cacheflush.h>
#include <asm/paca.h>
+#include <asm/lmb.h>
#include <asm/mmu.h>
#include <asm/sections.h> /* _end */
#include <asm/prom.h>
@@ -335,9 +336,102 @@ static void __init export_htab_values(vo
of_node_put(node);
}
+static struct property crashk_base_prop = {
+ .name = "linux,crashkernel-base",
+ .length = sizeof(unsigned long),
+ .value = (unsigned char *)&crashk_res.start,
+};
+
+static unsigned long crashk_size;
+
+static struct property crashk_size_prop = {
+ .name = "linux,crashkernel-size",
+ .length = sizeof(unsigned long),
+ .value = (unsigned char *)&crashk_size,
+};
+
+static void __init export_crashk_values(void)
+{
+ struct device_node *node;
+ struct property *prop;
+
+ node = of_find_node_by_path("/chosen");
+ if (!node)
+ return;
+
+ /* There might be existing crash kernel properties, but we can't
+ * be sure what's in them, so remove them. */
+ prop = of_find_property(node, "linux,crashkernel-base", NULL);
+ if (prop)
+ prom_remove_property(node, prop);
+
+ prop = of_find_property(node, "linux,crashkernel-size", NULL);
+ if (prop)
+ prom_remove_property(node, prop);
+
+ if (crashk_res.start != 0) {
+ prom_add_property(node, &crashk_base_prop);
+ crashk_size = crashk_res.end - crashk_res.start + 1;
+ prom_add_property(node, &crashk_size_prop);
+ }
+
+ of_node_put(node);
+}
+
void __init kexec_setup(void)
{
export_htab_values();
+ export_crashk_values();
+}
+
+static int __init early_parse_crashk(char *p)
+{
+ unsigned long size;
+
+ if (!p)
+ return 1;
+
+ size = memparse(p, &p);
+
+ if (*p == '@')
+ crashk_res.start = memparse(p + 1, &p);
+ else
+ crashk_res.start = KDUMP_KERNELBASE;
+
+ crashk_res.end = crashk_res.start + size - 1;
+
+ return 0;
+}
+early_param("crashkernel", early_parse_crashk);
+
+void __init reserve_crashkernel(void)
+{
+ unsigned long size;
+
+ if (crashk_res.start == 0)
+ return;
+
+ /* We might have got these values via the command line or the
+ * device tree, either way sanitise them now. */
+
+ size = crashk_res.end - crashk_res.start + 1;
+
+ if (crashk_res.start != KDUMP_KERNELBASE)
+ printk("Crash kernel location must be 0x%x\n",
+ KDUMP_KERNELBASE);
+
+ crashk_res.start = KDUMP_KERNELBASE;
+ size = PAGE_ALIGN(size);
+ crashk_res.end = crashk_res.start + size - 1;
+
+ /* Crash kernel trumps memory limit */
+ if (memory_limit && memory_limit <= crashk_res.end) {
+ memory_limit = crashk_res.end + 1;
+ printk("Adjusted memory limit for crashkernel, now 0x%lx\n",
+ memory_limit);
+ }
+
+ lmb_reserve(crashk_res.start, size);
}
int overlaps_crashkernel(unsigned long start, unsigned long size)
Index: to-merge/arch/powerpc/kernel/prom.c
===================================================================
--- to-merge.orig/arch/powerpc/kernel/prom.c
+++ to-merge/arch/powerpc/kernel/prom.c
@@ -1327,6 +1327,7 @@ void __init early_init_devtree(void *par
/* Reserve LMB regions used by kernel, initrd, dt, etc... */
lmb_reserve(PHYSICAL_START, __pa(klimit) - PHYSICAL_START);
reserve_kdump_trampoline();
+ reserve_crashkernel();
early_reserve_mem();
lmb_enforce_memory_limit(memory_limit);
Index: to-merge/arch/powerpc/kernel/prom_init.c
===================================================================
--- to-merge.orig/arch/powerpc/kernel/prom_init.c
+++ to-merge/arch/powerpc/kernel/prom_init.c
@@ -200,11 +200,6 @@ static unsigned long __initdata alloc_bo
static unsigned long __initdata rmo_top;
static unsigned long __initdata ram_top;
-#ifdef CONFIG_KEXEC
-static unsigned long __initdata prom_crashk_base;
-static unsigned long __initdata prom_crashk_size;
-#endif
-
static struct mem_map_entry __initdata mem_reserve_map[MEM_RESERVE_MAP_SIZE];
static int __initdata mem_reserve_cnt;
@@ -591,35 +586,6 @@ static void __init early_cmdline_parse(v
RELOC(iommu_force_on) = 1;
}
#endif
-
-#ifdef CONFIG_KEXEC
- /*
- * crashkernel=size@addr specifies the location to reserve for
- * crash kernel.
- */
- opt = strstr(RELOC(prom_cmd_line), RELOC("crashkernel="));
- if (opt) {
- opt += 12;
- RELOC(prom_crashk_size) =
- prom_memparse(opt, (const char **)&opt);
-
- if (ALIGN(RELOC(prom_crashk_size), 0x1000000) !=
- RELOC(prom_crashk_size)) {
- prom_printf("Warning: crashkernel size is not "
- "aligned to 16MB\n");
- }
-
- /*
- * At present, the crash kernel always run at 32MB.
- * Just ignore whatever user passed.
- */
- RELOC(prom_crashk_base) = 0x2000000;
- if (*opt == '@') {
- prom_printf("Warning: PPC64 kdump kernel always runs "
- "at 32 MB\n");
- }
- }
-#endif
}
#ifdef CONFIG_PPC_PSERIES
@@ -1122,12 +1088,6 @@ static void __init prom_init_mem(void)
prom_printf(" alloc_top_hi : %x\n", RELOC(alloc_top_high));
prom_printf(" rmo_top : %x\n", RELOC(rmo_top));
prom_printf(" ram_top : %x\n", RELOC(ram_top));
-#ifdef CONFIG_KEXEC
- if (RELOC(prom_crashk_base)) {
- prom_printf(" crashk_base : %x\n", RELOC(prom_crashk_base));
- prom_printf(" crashk_size : %x\n", RELOC(prom_crashk_size));
- }
-#endif
}
@@ -2187,10 +2147,6 @@ unsigned long __init prom_init(unsigned
*/
prom_init_mem();
-#ifdef CONFIG_KEXEC
- if (RELOC(prom_crashk_base))
- reserve_mem(RELOC(prom_crashk_base), RELOC(prom_crashk_size));
-#endif
/*
* Determine which cpu is actually running right _now_
*/
@@ -2243,16 +2199,6 @@ unsigned long __init prom_init(unsigned
}
#endif
-#ifdef CONFIG_KEXEC
- if (RELOC(prom_crashk_base)) {
- prom_setprop(_prom->chosen, "/chosen", "linux,crashkernel-base",
- PTRRELOC(&prom_crashk_base),
- sizeof(RELOC(prom_crashk_base)));
- prom_setprop(_prom->chosen, "/chosen", "linux,crashkernel-size",
- PTRRELOC(&prom_crashk_size),
- sizeof(RELOC(prom_crashk_size)));
- }
-#endif
/*
* Fixup any known bugs in the device-tree
*/
Index: to-merge/include/asm-powerpc/kexec.h
===================================================================
--- to-merge.orig/include/asm-powerpc/kexec.h
+++ to-merge/include/asm-powerpc/kexec.h
@@ -133,6 +133,8 @@ static inline int overlaps_crashkernel(u
return 0;
}
+static inline void reserve_crashkernel(void) { ; }
+
#endif /* CONFIG_KEXEC */
#endif /* ! __ASSEMBLY__ */
#endif /* __KERNEL__ */
^ permalink raw reply
* Re: [SOLVED] MPC8248 goes berserk when printing messages on the SMC console
From: Laurent Pinchart @ 2006-05-17 9:38 UTC (permalink / raw)
To: linuxppc-embedded
In-Reply-To: <200605161709.36387.laurent.pinchart@tbox.biz>
http://www.freescale.com/webapp/sps/utils/SingleFaq.jsp?FAQ-11241.xml
"Question. Board hangs when the 8260 data cache is enabled.
Answer. We have found that in some systems, changing the pipline depth to 0
in the BCR register (BCR[PLDP] = 1) fixes any data cache issues."
I don't know what those "some systems" are, but it seems that on all the
others the pipeline depth must be set to 1 (BCR[PLDP] = 0). I don't know why,
and it seems that Freescale doesn't either.
Laurent Pinchart
^ permalink raw reply
* Does the CPM uart driver need more memory barriers ?
From: Laurent Pinchart @ 2006-05-17 9:43 UTC (permalink / raw)
To: linuxppc-embedded
[-- Attachment #1: Type: text/plain, Size: 271 bytes --]
Hi everybody,
while trying to debug a serial port issue, I found out some code might seemed
to miss write memory barriers. Does the volatile keyword guarantee that gcc
will no reorder memory writes, or is the attached patched needed ?
Best regards,
Laurent Pinchart
[-- Attachment #2: cpm_wmb.patch --]
[-- Type: text/x-diff, Size: 578 bytes --]
diff --git a/drivers/serial/cpm_uart/cpm_uart_core.c b/drivers/serial/cpm_uart/cpm_uart_core.c
index b7bf4c6..545f56c 100644
--- a/drivers/serial/cpm_uart/cpm_uart_core.c
+++ b/drivers/serial/cpm_uart/cpm_uart_core.c
@@ -1032,6 +1032,7 @@ static void cpm_uart_console_write(struc
*cp = *s;
bdp->cbd_datlen = 1;
+ wmb();
bdp->cbd_sc |= BD_SC_READY;
if (bdp->cbd_sc & BD_SC_WRAP)
@@ -1048,6 +1049,7 @@ static void cpm_uart_console_write(struc
*cp = 13;
bdp->cbd_datlen = 1;
+ wmb();
bdp->cbd_sc |= BD_SC_READY;
if (bdp->cbd_sc & BD_SC_WRAP)
^ permalink raw reply related
* Re: [Alsa-devel] [RFC] alsa integer control ranges
From: Takashi Iwai @ 2006-05-17 9:47 UTC (permalink / raw)
To: Benjamin Herrenschmidt
Cc: linuxppc-dev list, Johannes Berg, ALSA development,
Jaroslav Kysela, Benjamin Berg
In-Reply-To: <1147817058.6753.7.camel@localhost.localdomain>
At Wed, 17 May 2006 08:04:18 +1000,
Benjamin Herrenschmidt wrote:
>
> On Tue, 2006-05-16 at 14:31 +0200, Jaroslav Kysela wrote:
> > On Tue, 16 May 2006, Johannes Berg wrote:
> >
> > > Apparently all alsa userspace programs including alsamixer suck. Hence,
> > > this patch is required to make them work properly. Why is it so hard to
> > > do these additions/subtractions in the program or maybe even in the alsa
> > > library? The alsa libraries already think they know better and mess up
> > > all kinds of things.
> >
> > It's better to fix apps, if they are broken.
>
> Problem is, they are currently all broken (pretty much) and thus unless
> we want to release a driver that won't work with any current distros,
> we'll have to whack the ranges :(
Well that's a big dilemma. Surely you want a stable driver and don't
want to see the flood of bug reports.
The mixer stuff is the weakest point in the current ALSA
implementation, and it has to be sorted out better.
Almost all existing mixer GUI programs are more or less buggy or quite
user-unfriendly.
Takashi
^ permalink raw reply
* [RFC PATCH 00/09] robust VM per_cpu variables
From: Steven Rostedt @ 2006-05-17 9:54 UTC (permalink / raw)
To: LKML
Cc: Andrew Morton, linux-mips, David Mosberger-Tang, linux-ia64,
Martin Mares, spyro, Joe Taylor, linuxppc-dev, Paul Mackerras,
benedict.gaster, sam, bjornw, kenneth.w.chen, Ingo Molnar, kiran,
clameter, Nick Piggin, grundler, arnd, Rusty Russell, starvik,
Linus Torvalds, Thomas Gleixner, rth, Chris Zankel, tony.luck,
Andi Kleen, ralf, Marc Gauthier, lethal, schwidefsky, linux390,
davem, parisc-linux
[-- Attachment #1: Type: TEXT/PLAIN, Size: 3716 bytes --]
(I tried to include all arch maintianers and memory maintainers, if I
missed someone, please let me know).
OK, as promised, I've got a working VM percpu solution.
A little history: please read the thread http://lkml.org/lkml/2006/4/14/137
Preivously I noticed that the per_cpu variables were implemented with
a little hack that Rusty Russell wrote up. I call it a hack because,
for modules an arbitrary number was used to hold all per_cpu variables
that would be used in the kernel or in modules. If this number was too
big, you waste the memory that is allocated for it, and if it were
too small, you wont be able to load all the modules that are required.
My first attempt to fix this introduced another dereference, to allow
for modules to allocate their own memory. This was quickly shot down,
and for good reason, because dereferences kill performance, and don't
play nice with large SMP systems that depend on per_cpu being fast.
But that first attempt had one benefit. It had good responses on how
to actually fix the problem. Without the first try, I would not have
tried this approach.
So what is this solution?
I now place the per_cpu variables into VM, such that the pages are
only allocated when needed. All the architecture needs to do is
supply a VM address range, size for each CPU to use (note this
implementation expects all the VM CPU areas to be together), and
three functions to allow for allocating page tables at bootup.
The bootup page table allocations are needed because the percpu
variables are used before the system initializes the memory. So
bootmem is needed to load the page tables.
But that's it! No more hacks for architectures with lots of CPUS and
NUMA to implement their own percpu algorithms. They just supply the
functions to allocate the variables, and the memory will be loaded
appropriately. So all architectures with VM can use the generic
solution. Does the main-line kernel support any architectures that
doesn't have a VM?
Another benefit is that this approach removes the one redirection that
was already used in the generic solution. Since the virtual memory
of the architecture for the per_cpu is static, the calculation to
find the variable is done with constants. So this is another win
for this solution.
So the three things that this patch gives us are:
1) Robust per_cpu. No more guessing the size of the per_cpu variable
sections. Default VM is 1 Meg per cpu. If you use more than that
I guess you're SOL.
2) Generic solution for all architectures. No more fancy hacks to
get the per cpu offest.
3) Removes the inderection of the current per_cpu generic solution.
The set of patches that I have implement this for the i386 architecture
and lays out the ground work for others. The i386 implementation I did
was mainly a hack. Nick Piggin suggested that before I do too much
work, I should post my stuff and ask for comments. That's what I'm doing
now.
This patch set works for my laptop (P4 HT) and I haven't tested
it with any other configuration. The implementation that I did for
i386 was only to get it to work and is not flexible. It needs much
better implementation, and I ask for help with that.
I basically concentrated to the core stuff. Another nice thing about
this patch set is that it needs both CONFIG_HAS_VM_PERCPU and
__ARCH_HAS_VM_PERCPU to take advantage of the VM percpu implementation.
Without these set, it defaults to the old usage. This way we
can slowly implement each architecture, in an incremental fashion.
OK, let'er rip. Flame me, yell at me, curse me, but please give me
feedback.
Thanks for your time.
-- Steve
PS. The attached module was used to test this patch on my laptop.
[-- Attachment #2: Type: TEXT/x-csrc, Size: 666 bytes --]
#include <linux/module.h>
#include <linux/percpu.h>
#define BIG_SIZE 4200
DEFINE_PER_CPU(int , bigarray[BIG_SIZE]);
static int __init percpu_init(void)
{
int i, x;
for (i=0; cpu_possible(i) ; i++)
for (x=0; x < BIG_SIZE; x++) {
if (!(x%100))
printk("processing %d:%d\n", i, x);
per_cpu(bigarray[x], i) = 10000 + x;
}
return 0;
}
static void __exit percpu_exit(void)
{
int i;
for (i=1; i < BIG_SIZE; i <<= 1)
printk("bigarray[%d] = %d\n", i, per_cpu(bigarray[i],0));
}
module_init(percpu_init);
module_exit(percpu_exit);
MODULE_AUTHOR("My name here");
MODULE_DESCRIPTION("percpu!");
MODULE_LICENSE("GPL");
^ permalink raw reply
* [RFC PATCH 01/09] robust VM per_cpu core
From: Steven Rostedt @ 2006-05-17 9:56 UTC (permalink / raw)
To: LKML
Cc: Andrew Morton, linux-mips, David Mosberger-Tang, linux-ia64,
Martin Mares, spyro, Joe Taylor, linuxppc-dev, Paul Mackerras,
benedict.gaster, sam, bjornw, kenneth.w.chen, Ingo Molnar, kiran,
clameter, Nick Piggin, grundler, arnd, Rusty Russell, starvik,
Linus Torvalds, Thomas Gleixner, rth, Chris Zankel, tony.luck,
Andi Kleen, ralf, Marc Gauthier, lethal, schwidefsky, linux390,
davem, parisc-linux
In-Reply-To: <Pine.LNX.4.58.0605170547490.8408@gandalf.stny.rr.com>
This is the VM per_cpu core patch. It includes the mm/per_cpu.c file
that is used to initialize and update per_cpu variables at startup
and module load.
To use this, the arch must define CONFIG_HAS_VM_PERCPU and
__ARCH_HAS_VM_PERCPU.
Also the following must be defined:
PERCPU_START - start of the percpu VM area
PERCPU_SIZE - size of the percpu VM area for each CPU so that the
total size would be PERCPU_SIZE * NR_CPUS
As well as the following three functions:
pud_t *pud_boot_alloc(struct mm_struct *mm, pgd_t *pgd, unsigned long addr,
int cpu);
pmd_t *pmd_boot_alloc(struct mm_struct *mm, pud_t *pud, unsigned long addr,
int cpu);
pte_t *pte_boot_alloc(struct mm_struct *mm, pmd_t *pmd, unsigned long addr,
int cpu);
The above functions are to allocate page tables from bootmem because the
percpu is initialized right after setup_arch in init/main.c
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Index: linux-2.6.16-test/mm/percpu.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.16-test/mm/percpu.c 2006-05-17 04:39:52.000000000 -0400
@@ -0,0 +1,287 @@
+/*
+ * linux/mm/percpu.c
+ *
+ * Copyright (C) 2006 Steven Rostedt <rostedt@goodmis.org>
+ *
+ * Some of this code was influenced by mm/vmalloc.c
+ *
+ * The percpu variables need to always have the same offset from one CPU to
+ * the next no matter if the percpu variable is defined in the kernel or
+ * inside a module. So to guarentee that the offset is the same for both,
+ * they are mapped into virtual memory.
+ *
+ * Since the percpu variables are used before memory is initialized, the
+ * inital setup must be done with bootmem, and thus vmalloc code can not be
+ * used.
+ *
+ * Credits:
+ * -------
+ * This goes to lots of people that inspired me on LKML, and responded to
+ * my first (horrible) implementation of robust per_cpu variables.
+ *
+ * Also many thanks to Rusty Russell in his generic per_cpu implementation.
+ */
+
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/highmem.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+
+#include <linux/bootmem.h>
+
+#include <asm/uaccess.h>
+#include <asm/tlbflush.h>
+
+static int __init percpu_boot_alloc(unsigned long addr, unsigned long size,
+ int node);
+
+/*
+ * percpu_allocated keeps track of the actual allocated memory. It
+ * always points to the page after the last page in VM that was allocated.
+ *
+ * Yes this is also a per_cpu variable :)
+ * It gets updated after the copys are made.
+ */
+static DEFINE_PER_CPU(unsigned long, percpu_allocated);
+
+static char * __init per_cpu_allocate_init(unsigned long size, int cpu)
+{
+ unsigned long addr;
+
+ addr = PERCPU_START+(cpu*PERCPU_SIZE);
+ BUG_ON(percpu_boot_alloc(addr, size, cpu));
+
+ return (char*)addr;
+
+}
+
+/**
+ * setup_per_cpu_areas - initialization of VM per_cpu variables
+ *
+ * Allocate pages in VM for the per_cpu variables
+ * of the kernel.
+ */
+void __init setup_per_cpu_areas(void)
+{
+ unsigned long size, i;
+ char *ptr;
+
+ /* Copy section for each CPU (we discard the original) */
+ size = ALIGN(__per_cpu_end - __per_cpu_start, SMP_CACHE_BYTES);
+
+ for (i = 0; i < NR_CPUS; i++, ptr += size) {
+ ptr = per_cpu_allocate_init(size, i);
+ memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start);
+ wmb();
+ per_cpu(percpu_allocated, i) =
+ PAGE_ALIGN((unsigned long)ptr + size);
+ }
+}
+
+static __init int percpu_boot_pte_alloc(pmd_t *pmd, unsigned long addr,
+ unsigned long end, int node)
+{
+ pte_t *pte;
+
+ pte = pte_boot_alloc(&init_mm, pmd, addr, node);
+ if (!pte)
+ return -ENOMEM;
+ do {
+ void *page;
+ WARN_ON(!pte_none(*pte));
+ page = alloc_bootmem_pages(PAGE_SIZE);
+ if (!page)
+ return -ENOMEM;
+ set_pte_at(&init_mm, addr, pte, mk_pte(virt_to_page(page),
+ PAGE_KERNEL));
+ } while (pte++, addr += PAGE_SIZE, addr < end);
+ return 0;
+}
+
+static __init int percpu_boot_pmd_alloc(pud_t *pud, unsigned long addr,
+ unsigned long end, int node)
+{
+ pmd_t *pmd;
+ unsigned long next;
+
+ pmd = pmd_boot_alloc(&init_mm, pud, addr, node);
+ if (!pud)
+ return -ENOMEM;
+ do {
+ next = pmd_addr_end(addr, end);
+ if (percpu_boot_pte_alloc(pmd, addr, next, node))
+ return -ENOMEM;
+ } while (pmd++, addr = next, addr < end);
+ return 0;
+}
+
+static __init int percpu_boot_pud_alloc(pgd_t *pgd, unsigned long addr,
+ unsigned long end, int node)
+{
+ pud_t *pud;
+ unsigned long next;
+
+ pud = pud_boot_alloc(&init_mm, pgd, addr, node);
+ if (!pud)
+ return -ENOMEM;
+ do {
+ next = pud_addr_end(addr, end);
+ if (percpu_boot_pmd_alloc(pud, addr, next, node))
+ return -ENOMEM;
+ } while (pud++, addr = next, addr < end);
+ return 0;
+}
+
+static int __init percpu_boot_alloc(unsigned long addr, unsigned long size,
+ int node)
+{
+ pgd_t *pgd;
+ unsigned long end = addr + size;
+ unsigned long next;
+ int err;
+
+ pgd = pgd_offset_k(addr);
+ do {
+ next = pgd_addr_end(addr, end);
+ err = percpu_boot_pud_alloc(pgd, addr, next, node);
+ if (err)
+ break;
+ } while (pgd++, addr = next, addr < end);
+ return err;
+}
+
+static __init int percpu_pte_alloc(pmd_t *pmd, unsigned long addr,
+ unsigned long end, int node)
+{
+ pte_t *pte;
+
+ pte = pte_alloc_kernel(pmd, addr);
+ if (!pte)
+ return -ENOMEM;
+ do {
+ void *page;
+ if (unlikely(!pte_none(*pte))) {
+ printk("bad pte: %p->%p\n", pte, (void*)pte_val(*pte));
+ BUG();
+ return -EFAULT;
+ }
+ page = (void*)__get_free_page(GFP_KERNEL);
+ if (!page)
+ return -ENOMEM;
+ set_pte_at(&init_mm, addr, pte, mk_pte(virt_to_page(page),
+ PAGE_KERNEL));
+ } while (pte++, addr += PAGE_SIZE, addr < end);
+ __flush_tlb();
+ return 0;
+}
+
+static __init int percpu_pmd_alloc(pud_t *pud, unsigned long addr,
+ unsigned long end, int node)
+{
+ pmd_t *pmd;
+ unsigned long next;
+
+ pmd = pmd_alloc(&init_mm, pud, addr);
+ if (!pmd)
+ return -ENOMEM;
+ do {
+ next = pmd_addr_end(addr, end);
+ if (percpu_pte_alloc(pmd, addr, next, node))
+ return -ENOMEM;
+ } while (pmd++, addr = next, addr < end);
+ return 0;
+}
+
+static __init int percpu_pud_alloc(pgd_t *pgd, unsigned long addr,
+ unsigned long end, int node)
+{
+ pud_t *pud;
+ unsigned long next;
+
+ pud = pud_alloc(&init_mm, pgd, addr);
+ if (!pud)
+ return -ENOMEM;
+ do {
+ next = pud_addr_end(addr, end);
+ if (percpu_pmd_alloc(pud, addr, next, node))
+ return -ENOMEM;
+ } while (pud++, addr = next, addr < end);
+ return 0;
+}
+
+static int percpu_alloc(unsigned long addr, unsigned long size,
+ int node)
+{
+ pgd_t *pgd;
+ unsigned long end = addr + size;
+ unsigned long next;
+ int err;
+
+ pgd = pgd_offset_k(addr);
+ do {
+ next = pgd_addr_end(addr, end);
+ err = percpu_pud_alloc(pgd, addr, next, node);
+ if (err)
+ break;
+ } while (pgd++, addr = next, addr < end);
+ return err;
+}
+
+static int percpu_module_update(void *pcpudst, unsigned long size, int cpu)
+{
+ int err = 0;
+ /*
+ * These two local variables are only used to keep the code
+ * looking simpler. Since this function is only called on
+ * module load, it's not time critical.
+ */
+ unsigned long needed_address = (unsigned long)
+ ((pcpudst) + __PERCPU_OFFSET_ADDRESS(cpu)+size);
+ unsigned long allocated = per_cpu(percpu_allocated, cpu);
+
+ if (allocated < needed_address) {
+ unsigned long alloc = needed_address - allocated;
+ err = percpu_alloc(allocated, alloc, cpu);
+ if (!err)
+ per_cpu(percpu_allocated, cpu) =
+ PAGE_ALIGN(needed_address);
+ }
+ return err;
+}
+
+/**
+ * per_cpu_modcopy - copy and allocate module VM per_cpu variables
+ *
+ * @pcpudst: Destination of module per_cpu section
+ * @src: Source of module per_cpu data section
+ * @size: Size of module per_cpu data section
+ *
+ * Copy the module's data per_cpu section into each VM per_cpu section
+ * stored in the kernel. If need be, allocate more pages in VM
+ * if they are not yet allocated.
+ *
+ * protected by module_mutex
+ */
+int percpu_modcopy(void *pcpudst, void *src, unsigned long size)
+{
+ unsigned int i;
+ int err = 0;
+
+ for (i = 0; i < NR_CPUS; i++)
+ if (cpu_possible(i)) {
+ err = percpu_module_update(pcpudst, size, i);
+ if (err)
+ break;
+ memcpy((pcpudst)+__PERCPU_OFFSET_ADDRESS(i),
+ (src), (size));
+ }
+ return err;
+}
+
+/*
+ * We use the __per_cpu_start for the indexing of
+ * per_cpu variables, even in modules.
+ */
+EXPORT_SYMBOL(__per_cpu_start);
Index: linux-2.6.16-test/mm/Makefile
===================================================================
--- linux-2.6.16-test.orig/mm/Makefile 2006-05-17 04:32:27.000000000 -0400
+++ linux-2.6.16-test/mm/Makefile 2006-05-17 04:39:52.000000000 -0400
@@ -22,3 +22,4 @@ obj-$(CONFIG_SLOB) += slob.o
obj-$(CONFIG_SLAB) += slab.o
obj-$(CONFIG_MEMORY_HOTPLUG) += memory_hotplug.o
obj-$(CONFIG_FS_XIP) += filemap_xip.o
+obj-$(CONFIG_HAS_VM_PERCPU) += percpu.o
\ No newline at end of file
^ permalink raw reply
* [RFC PATCH 01/09] robust VM per_cpu mm header update
From: Steven Rostedt @ 2006-05-17 9:56 UTC (permalink / raw)
To: LKML
Cc: Andrew Morton, linux-mips, David Mosberger-Tang, linux-ia64,
Martin Mares, spyro, Joe Taylor, linuxppc-dev, Paul Mackerras,
benedict.gaster, sam, bjornw, kenneth.w.chen, Ingo Molnar, kiran,
clameter, Nick Piggin, grundler, arnd, Rusty Russell, starvik,
Linus Torvalds, Thomas Gleixner, rth, Chris Zankel, tony.luck,
Andi Kleen, ralf, Marc Gauthier, lethal, schwidefsky, linux390,
davem, parisc-linux
In-Reply-To: <Pine.LNX.4.58.0605170547490.8408@gandalf.stny.rr.com>
This patch declares the three functions needed by the archs to
implement the percpu VM.
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Index: linux-2.6.16-test/include/linux/mm.h
===================================================================
--- linux-2.6.16-test.orig/include/linux/mm.h 2006-05-17 04:32:27.000000000 -0400
+++ linux-2.6.16-test/include/linux/mm.h 2006-05-17 04:56:52.000000000 -0400
@@ -795,6 +795,15 @@ int __pmd_alloc(struct mm_struct *mm, pu
int __pte_alloc(struct mm_struct *mm, pmd_t *pmd, unsigned long address);
int __pte_alloc_kernel(pmd_t *pmd, unsigned long address);
+#ifdef CONFIG_HAS_VM_PERCPU
+pud_t *pud_boot_alloc(struct mm_struct *mm, pgd_t *pgd, unsigned long addr,
+ int cpu);
+pmd_t *pmd_boot_alloc(struct mm_struct *mm, pud_t *pud, unsigned long addr,
+ int cpu);
+pte_t *pte_boot_alloc(struct mm_struct *mm, pmd_t *pmd, unsigned long addr,
+ int cpu);
+#endif
+
/*
* The following ifdef needed to get the 4level-fixup.h header to work.
* Remove it when 4level-fixup.h has been removed.
^ permalink raw reply
* [RFC PATCH 03/09] robust VM per_cpu generic header
From: Steven Rostedt @ 2006-05-17 9:57 UTC (permalink / raw)
To: LKML
Cc: Andrew Morton, linux-mips, David Mosberger-Tang, linux-ia64,
Martin Mares, spyro, Joe Taylor, linuxppc-dev, Paul Mackerras,
benedict.gaster, sam, bjornw, kenneth.w.chen, Ingo Molnar, kiran,
clameter, Nick Piggin, grundler, arnd, Rusty Russell, starvik,
Linus Torvalds, Thomas Gleixner, rth, Chris Zankel, tony.luck,
Andi Kleen, ralf, Marc Gauthier, lethal, schwidefsky, linux390,
davem, parisc-linux
In-Reply-To: <Pine.LNX.4.58.0605170547490.8408@gandalf.stny.rr.com>
This patch adds the VM per_cpu to the generic per_cpu.h header.
If __ARCH_HAS_VM_PERCPU is defined, it is expected that the arch
also defined the following:
PERCPU_START - start of VM area that per_cpu variables will be stored.
PERCPU_SIZE - size of the VM area for each CPU. So the total size
would be PERCPU_SIZE * NR_CPU
If __ARCH_HAS_VM_PERCPU is not defined, it falls back to the old
percpu hack.
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Index: linux-2.6.16-test/include/asm-generic/percpu.h
===================================================================
--- linux-2.6.16-test.orig/include/asm-generic/percpu.h 2006-05-17 04:32:27.000000000 -0400
+++ linux-2.6.16-test/include/asm-generic/percpu.h 2006-05-17 04:57:21.000000000 -0400
@@ -5,25 +5,52 @@
#define __GENERIC_PER_CPU
#ifdef CONFIG_SMP
-extern unsigned long __per_cpu_offset[NR_CPUS];
-
/* Separate out the type, so (int[3], foo) works. */
#define DEFINE_PER_CPU(type, name) \
__attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name
+#ifdef __ARCH_HAS_VM_PERCPU
+
+#include <asm/sections.h>
+
+/*
+ * This is included in linux/percpu.h and if PERCPU_ENOUGH_ROOM is already
+ * defined, it wont overwrite it.
+ * This allows kernel/module.c to be the same for both archs with VM
+ * per_cpu and without.
+ */
+#define PERCPU_ENOUGH_ROOM PERCPU_SIZE
+
+#define __PERCPU_OFFSET_ADDRESS(i) ((PERCPU_START+PERCPU_SIZE*(i)) - \
+ (unsigned long)__per_cpu_start)
+
+extern void setup_per_cpu_areas (void);
+extern int percpu_modcopy(void *pcpudst, void *src, unsigned long size);
+
+#else /* !__ARCH_HAS_VM_PERCPU */
+
+extern unsigned long __per_cpu_offset[NR_CPUS];
+
+#define __PERCPU_OFFSET_ADDRESS(i) __per_cpu_offset[i]
+
+/* A macro to avoid #include hell... */
+#define percpu_modcopy(pcpudst, src, size) \
+({ \
+ unsigned int __i; \
+ for (__i = 0; __i < NR_CPUS; __i++) \
+ if (cpu_possible(__i)) \
+ memcpy((pcpudst)+__PERCPU_OFFSET_ADDRESS(__i), \
+ (src), (size)); \
+ 0; \
+})
+
+#endif /* __ARCH_HAS_VM_PERCPU */
+
/* var is in discarded region: offset to particular copy we want */
-#define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))
+#define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, \
+ __PERCPU_OFFSET_ADDRESS(cpu)))
#define __get_cpu_var(var) per_cpu(var, smp_processor_id())
-/* A macro to avoid #include hell... */
-#define percpu_modcopy(pcpudst, src, size) \
-do { \
- unsigned int __i; \
- for (__i = 0; __i < NR_CPUS; __i++) \
- if (cpu_possible(__i)) \
- memcpy((pcpudst)+__per_cpu_offset[__i], \
- (src), (size)); \
-} while (0)
#else /* ! SMP */
#define DEFINE_PER_CPU(type, name) \
^ permalink raw reply
* [RFC PATCH 04/09] robust VM per_cpu main startup
From: Steven Rostedt @ 2006-05-17 9:58 UTC (permalink / raw)
To: LKML
Cc: Andrew Morton, linux-mips, David Mosberger-Tang, linux-ia64,
Martin Mares, spyro, Joe Taylor, linuxppc-dev, Paul Mackerras,
benedict.gaster, sam, bjornw, kenneth.w.chen, Ingo Molnar, kiran,
clameter, Nick Piggin, grundler, arnd, Rusty Russell, starvik,
Linus Torvalds, Thomas Gleixner, rth, Chris Zankel, tony.luck,
Andi Kleen, ralf, Marc Gauthier, lethal, schwidefsky, linux390,
davem, parisc-linux
In-Reply-To: <Pine.LNX.4.58.0605170547490.8408@gandalf.stny.rr.com>
This patch disables the generic setup if __ARCH_HAS_VM_PERCPU defined.
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Index: linux-2.6.16-test/init/main.c
===================================================================
--- linux-2.6.16-test.orig/init/main.c 2006-05-17 04:32:28.000000000 -0400
+++ linux-2.6.16-test/init/main.c 2006-05-17 04:57:45.000000000 -0400
@@ -324,7 +324,7 @@ static inline void smp_prepare_cpus(unsi
#else
-#ifdef __GENERIC_PER_CPU
+#if defined(__GENERIC_PER_CPU) && !defined(__ARCH_HAS_VM_PERCPU)
unsigned long __per_cpu_offset[NR_CPUS];
EXPORT_SYMBOL(__per_cpu_offset);
^ permalink raw reply
* [RFC PATCH 05/09] robust VM per_cpu module
From: Steven Rostedt @ 2006-05-17 9:59 UTC (permalink / raw)
To: LKML
Cc: Andrew Morton, linux-mips, David Mosberger-Tang, linux-ia64,
Martin Mares, spyro, Joe Taylor, linuxppc-dev, Paul Mackerras,
benedict.gaster, sam, bjornw, kenneth.w.chen, Ingo Molnar, kiran,
clameter, Nick Piggin, grundler, arnd, Rusty Russell, starvik,
Linus Torvalds, Thomas Gleixner, rth, Chris Zankel, tony.luck,
Andi Kleen, ralf, Marc Gauthier, lethal, schwidefsky, linux390,
davem, parisc-linux
In-Reply-To: <Pine.LNX.4.58.0605170547490.8408@gandalf.stny.rr.com>
This patch performs a check on the return value of percpu_modcopy
for the module load.
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Index: linux-2.6.16-test/kernel/module.c
===================================================================
--- linux-2.6.16-test.orig/kernel/module.c 2006-05-17 04:32:28.000000000 -0400
+++ linux-2.6.16-test/kernel/module.c 2006-05-17 04:57:53.000000000 -0400
@@ -1819,8 +1819,11 @@ static struct module *load_module(void _
sort_extable(extable, extable + mod->num_exentries);
/* Finally, copy percpu area over. */
- percpu_modcopy(mod->percpu, (void *)sechdrs[pcpuindex].sh_addr,
- sechdrs[pcpuindex].sh_size);
+ err = percpu_modcopy(mod->percpu, (void *)sechdrs[pcpuindex].sh_addr,
+ sechdrs[pcpuindex].sh_size);
+
+ if (err < 0)
+ goto cleanup;
add_kallsyms(mod, sechdrs, symindex, strindex, secstrings);
^ permalink raw reply
* Re: [Alsa-devel] [RFC] alsa integer control ranges
From: Takashi Iwai @ 2006-05-17 9:59 UTC (permalink / raw)
To: Benjamin Herrenschmidt
Cc: linuxppc-dev list, Johannes Berg, ALSA development, Benjamin Berg
In-Reply-To: <1147817023.6753.5.camel@localhost.localdomain>
At Wed, 17 May 2006 08:03:43 +1000,
Benjamin Herrenschmidt wrote:
>
> On Tue, 2006-05-16 at 14:27 +0200, Takashi Iwai wrote:
> > At Tue, 16 May 2006 14:02:20 +0200,
> > Johannes Berg wrote:
> > >
> > > Apparently all alsa userspace programs including alsamixer suck. Hence,
> > > this patch is required to make them work properly. Why is it so hard to
> > > do these additions/subtractions in the program or maybe even in the alsa
> > > library? The alsa libraries already think they know better and mess up
> > > all kinds of things.
> >
> > It's a pretty stupid question to ask why you are stupid :)
> >
> > I don't think it's alsa-lib that prevents the negative or non-zero
> > integer range. The fact amixer works implies that it's an
> > app-specific bug. But I'm not 100% sure and need more
> > inside-looking.
>
> Well, the problem I think is that pretty much all apps but amixer (and
> alsamixer whch works too for me at least) are bogus. It would have been
> good if Alsa had a more explicit specification that those values are not
> to be interpreted in the old OSS range :) In fact, best would have been
> to have the control structure carry a "unit" which a set of known units,
> one being dB, since the natural way of specifying an attenuation on any
> serious audio HW is dB and is negative...
Yes, the dB expression has been discussed sometimes and we all agreed
that we definitely need it. But stuck at the implemenation detail.
We have several issuess regarding the conversion of dB <-> raw volume
values:
- some codecs use linear volume
- some codecs have non-linear dB slopes
- unknown dB information for some controllers and codecs
And, USB and HD-audio devices/codecs provide the dB information. Thus
the dB conversion table has to be evaluated dynamically for them.
So, it's not so easy as expected to create an API covering all
above...
Takashi
^ permalink raw reply
* [RFC PATCH 06/09] robust VM per_cpu i386 bootmem
From: Steven Rostedt @ 2006-05-17 10:00 UTC (permalink / raw)
To: LKML
Cc: Andrew Morton, linux-mips, David Mosberger-Tang, linux-ia64,
Martin Mares, spyro, Joe Taylor, linuxppc-dev, Paul Mackerras,
benedict.gaster, sam, bjornw, kenneth.w.chen, Ingo Molnar, kiran,
clameter, Nick Piggin, grundler, arnd, Rusty Russell, starvik,
Linus Torvalds, Thomas Gleixner, rth, Chris Zankel, tony.luck,
Andi Kleen, ralf, Marc Gauthier, lethal, schwidefsky, linux390,
davem, parisc-linux
In-Reply-To: <Pine.LNX.4.58.0605170547490.8408@gandalf.stny.rr.com>
This patch was to get my VM percpu working on my laptop. This patch
still needs work to handle NUMA and other types of X86 architectures.
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Index: linux-2.6.16-test/arch/i386/mm/init.c
===================================================================
--- linux-2.6.16-test.orig/arch/i386/mm/init.c 2006-05-17 04:32:28.000000000 -0400
+++ linux-2.6.16-test/arch/i386/mm/init.c 2006-05-17 04:58:37.000000000 -0400
@@ -772,3 +772,39 @@ void free_initrd_mem(unsigned long start
}
}
#endif
+
+/*
+ * The following three functions are to impement per_cpu variables
+ * into VM. per_cpu variables are initialized very early on startup
+ * and before memory management. So the per_cpu initialization needs
+ * a way to allocate pages using bootmem.
+ */
+pud_t __init *pud_boot_alloc(struct mm_struct *mm, pgd_t *pgd,
+ unsigned long addr, int cpu)
+{
+ return (pud_t*)pgd;
+}
+
+pmd_t __init *pmd_boot_alloc(struct mm_struct *mm, pud_t *pud,
+ unsigned long addr, int cpu)
+{
+ return pmd_offset(pud, addr);
+}
+
+/* FIXME - handle NUMA handling with the CPU parameter */
+pte_t __init *pte_boot_alloc(struct mm_struct *mm, pmd_t *pmd,
+ unsigned long addr, int cpu)
+{
+ pte_t *pte;
+
+ if (pmd_none(*pmd)) {
+ pte = alloc_bootmem_pages(PAGE_SIZE);
+ if (!pte)
+ return NULL;
+ mm->nr_ptes++;
+ set_pmd(pmd, __pmd(__pa(pte) | _PAGE_TABLE));
+ } else
+ pte = pte_offset_kernel(pmd, addr);
+
+ return pte;
+}
^ permalink raw reply
* [RFC PATCH 07/09] robust VM per_cpu i386 VM area
From: Steven Rostedt @ 2006-05-17 10:01 UTC (permalink / raw)
To: LKML
Cc: Andrew Morton, linux-mips, David Mosberger-Tang, linux-ia64,
Martin Mares, spyro, Joe Taylor, linuxppc-dev, Paul Mackerras,
benedict.gaster, sam, bjornw, kenneth.w.chen, Ingo Molnar, kiran,
clameter, Nick Piggin, grundler, arnd, Rusty Russell, starvik,
Linus Torvalds, Thomas Gleixner, rth, Chris Zankel, tony.luck,
Andi Kleen, ralf, Marc Gauthier, lethal, schwidefsky, linux390,
davem, parisc-linux
In-Reply-To: <Pine.LNX.4.58.0605170547490.8408@gandalf.stny.rr.com>
This patch allocates the percpu VM area using the fix addresses.
It defines currently 1 meg per cpu.
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Index: linux-2.6.16-test/include/asm-i386/fixmap.h
===================================================================
--- linux-2.6.16-test.orig/include/asm-i386/fixmap.h 2006-05-17 04:32:27.000000000 -0400
+++ linux-2.6.16-test/include/asm-i386/fixmap.h 2006-05-17 04:59:34.000000000 -0400
@@ -32,6 +32,10 @@
#include <asm/kmap_types.h>
#endif
+/* One meg per cpu of VM space */
+#define PERCPU_PAGES 256
+#define PERCPU_SIZE (PERCPU_PAGES << PAGE_SHIFT)
+
/*
* Here we define all the compile-time 'special' virtual
* addresses. The point is to have a constant address at
@@ -83,6 +87,8 @@ enum fixed_addresses {
#ifdef CONFIG_PCI_MMCONFIG
FIX_PCIE_MCFG,
#endif
+ FIX_PERCPU_BEGIN,
+ FIX_PERCPU_END = FIX_PERCPU_BEGIN+(PERCPU_PAGES*NR_CPUS)-1,
__end_of_permanent_fixed_addresses,
/* temporary boot-time mappings, used before ioremap() is functional */
#define NR_FIX_BTMAPS 16
^ permalink raw reply
* [RFC PATCH 08/09] robust VM per_cpu i386 header
From: Steven Rostedt @ 2006-05-17 10:01 UTC (permalink / raw)
To: LKML
Cc: Andrew Morton, linux-mips, David Mosberger-Tang, linux-ia64,
Martin Mares, spyro, Joe Taylor, linuxppc-dev, Paul Mackerras,
benedict.gaster, sam, bjornw, kenneth.w.chen, Ingo Molnar, kiran,
clameter, Nick Piggin, grundler, arnd, Rusty Russell, starvik,
Linus Torvalds, Thomas Gleixner, rth, Chris Zankel, tony.luck,
Andi Kleen, ralf, Marc Gauthier, lethal, schwidefsky, linux390,
davem, parisc-linux
In-Reply-To: <Pine.LNX.4.58.0605170547490.8408@gandalf.stny.rr.com>
This patch adds the __ARCH_HAS_VM_PERCPU to i386 and defines
the PERCPU_START macro.
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Index: linux-2.6.16-test/include/asm-i386/percpu.h
===================================================================
--- linux-2.6.16-test.orig/include/asm-i386/percpu.h 2006-05-17 04:32:27.000000000 -0400
+++ linux-2.6.16-test/include/asm-i386/percpu.h 2006-05-17 05:00:00.000000000 -0400
@@ -1,6 +1,16 @@
#ifndef __ARCH_I386_PERCPU__
#define __ARCH_I386_PERCPU__
+#ifdef CONFIG_HAS_VM_PERCPU
+#define __ARCH_HAS_VM_PERCPU
+#include <asm/fixmap.h>
+
+/*
+ * Virtual address space for the percpu area.
+ */
+#define PERCPU_START (__fix_to_virt(FIX_PERCPU_END))
+#endif /* CONFIG_HAS_VM_PERCPU */
+
#include <asm-generic/percpu.h>
#endif /* __ARCH_I386_PERCPU__ */
^ permalink raw reply
* [RFC PATCH 09/09] robust VM per_cpu i386 Kconfig update
From: Steven Rostedt @ 2006-05-17 10:01 UTC (permalink / raw)
To: LKML
Cc: Andrew Morton, linux-mips, David Mosberger-Tang, linux-ia64,
Martin Mares, spyro, Joe Taylor, linuxppc-dev, Paul Mackerras,
benedict.gaster, sam, bjornw, kenneth.w.chen, Ingo Molnar, kiran,
clameter, Nick Piggin, grundler, arnd, Rusty Russell, starvik,
Linus Torvalds, Thomas Gleixner, rth, Chris Zankel, tony.luck,
Andi Kleen, ralf, Marc Gauthier, lethal, schwidefsky, linux390,
davem, parisc-linux
In-Reply-To: <Pine.LNX.4.58.0605170547490.8408@gandalf.stny.rr.com>
This patch forces the CONFIG_HAS_VM_PERCU to be defined for i386.
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Index: linux-2.6.16-test/arch/i386/Kconfig
===================================================================
--- linux-2.6.16-test.orig/arch/i386/Kconfig 2006-05-17 04:32:27.000000000 -0400
+++ linux-2.6.16-test/arch/i386/Kconfig 2006-05-17 05:00:10.000000000 -0400
@@ -1116,3 +1116,7 @@ config X86_TRAMPOLINE
config KTIME_SCALAR
bool
default y
+
+config HAS_VM_PERCPU
+ bool
+ default y
\ No newline at end of file
^ permalink raw reply
* Is isp1362 compatible to OHCI
From: Hsia Jason @ 2006-05-17 9:56 UTC (permalink / raw)
To: linuxppc-embedded
Hi All,
Nice to meet you.
Is isp1362 compatible to OHCI. The dirvers of 1362 in the
kernel linuxppc_2_4_devel are base on the OHCI.Am I wright?
However, if 1362 is compatible to OHCI,why some bits in the
Control REGs are missed,and why all the Memory REGs are missed,while
some other REGs ,such as ISO REGs ,are added?
^ permalink raw reply
* [PATCH/2.6.17-rc4 0/10] powerpc: add mpc7448hpc2 (Taiga) board support
From: Zang Roy-r61911 @ 2006-05-17 10:13 UTC (permalink / raw)
To: Paul Mackerras; +Cc: linuxppc-dev list, Alexandre.Bounine, Yang Xin-Xin-r48390
Hi, Paul
This series of patch adds mpc7448hpc2 (taiga) board support in arch/powerpc.
Tsi108 chip of Tundra Semiconductor is also supported.
Roy Zang
^ permalink raw reply
* [PATCH/2.6.17-rc4 1/10] Powerpc: Add general support for mpc7448h pc2 (Taiga) platform
From: Zang Roy-r61911 @ 2006-05-17 10:13 UTC (permalink / raw)
To: Paul Mackerras; +Cc: linuxppc-dev list, Alexandre.Bounine, Yang Xin-Xin-r48390
Add support for Freescale mpcc7448 (Taiga) board support
Signed-off-by: Roy Zang <tie-fei.zang@freescale.com>
---
arch/powerpc/Kconfig | 24 +++
arch/powerpc/kernel/legacy_serial.c | 39 ++++
arch/powerpc/platforms/74xx/Makefile | 4
arch/powerpc/platforms/74xx/misc.c | 38 ++++
arch/powerpc/platforms/74xx/mpc7448_hpc2.c | 257 ++++++++++++++++++++++++++++
arch/powerpc/platforms/74xx/mpc7448_hpc2.h | 80 +++++++++
arch/powerpc/platforms/74xx/pci.c | 103 +++++++++++
arch/powerpc/platforms/Makefile | 1
8 files changed, 545 insertions(+), 1 deletions(-)
create mode 100644 arch/powerpc/platforms/74xx/Makefile
create mode 100644 arch/powerpc/platforms/74xx/misc.c
create mode 100644 arch/powerpc/platforms/74xx/mpc7448_hpc2.c
create mode 100644 arch/powerpc/platforms/74xx/mpc7448_hpc2.h
create mode 100644 arch/powerpc/platforms/74xx/pci.c
13c16416da4fa51ec771bd4863d94344d139b2f5
diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig
index 6729c98..abeac1f 100644
--- a/arch/powerpc/Kconfig
+++ b/arch/powerpc/Kconfig
@@ -332,6 +332,27 @@ config APUS
Select APUS if configuring for a PowerUP Amiga.
More information is available at:
<http://linux-apus.sourceforge.net/>.
+
+config MPC7448HPC2
+ bool "Freescale MPC7448HPC2(Taiga)"
+ select DEFAULT_UIMAGE
+ select PPC_UDBG_16550
+ help
+ Select MPC7448HPC2 if configuring for Freescale MPC7448HPC2 (Taiga)
+ platform
+
+config 74xx
+ bool
+ depends on MPC7448HPC2
+ default y
+ help
+ Select 74xx support
+
+config TSI108_BRIDGE
+ bool
+ depends on MPC7448HPC2
+ default y
+
endchoice
config PPC_PSERIES
@@ -798,7 +819,8 @@ config MCA
bool
config PCI
- bool "PCI support" if 40x || CPM2 || PPC_83xx || PPC_85xx || PPC_MPC52xx || (EMBEDDED && PPC_ISERIES)
+ bool "PCI support" if 40x || CPM2 || PPC_83xx || PPC_85xx || PPC_MPC52xx || (EMBEDDED && PPC_ISERIES) \
+ || MPC7448HPC2
default y if !40x && !CPM2 && !8xx && !APUS && !PPC_83xx && !PPC_85xx
default PCI_PERMEDIA if !4xx && !CPM2 && !8xx && APUS
default PCI_QSPAN if !4xx && !CPM2 && 8xx
diff --git a/arch/powerpc/kernel/legacy_serial.c b/arch/powerpc/kernel/legacy_serial.c
index 6e67b5b..f99238d 100644
--- a/arch/powerpc/kernel/legacy_serial.c
+++ b/arch/powerpc/kernel/legacy_serial.c
@@ -134,6 +134,34 @@ static int __init add_legacy_soc_port(st
return add_legacy_port(np, -1, UPIO_MEM, addr, addr, NO_IRQ, flags);
}
+static int __init add_legacy_tsi_port(struct device_node *np,
+ struct device_node *tsi_dev)
+{
+ phys_addr_t addr;
+ u32 *addrp;
+ upf_t flags = UPF_BOOT_AUTOCONF | UPF_SKIP_TEST | UPF_SHARE_IRQ;
+
+ /* We only support ports that have a clock frequency properly
+ * encoded in the device-tree.
+ */
+ if (get_property(np, "clock-frequency", NULL) == NULL)
+ return -1;
+
+ /* Get the address */
+ addrp = of_get_address(tsi_dev, 0, NULL, NULL);
+ if (addrp == NULL)
+ return -1;
+
+ addr = of_translate_address(tsi_dev, addrp);
+
+ /* Add port, irq will be dealt with later. We passed a translated
+ * IO port value. It will be fixed up later along with the irq
+ */
+ return add_legacy_port(np, -1, UPIO_MEM, addr, addr, NO_IRQ, flags);
+}
+
+
+
static int __init add_legacy_isa_port(struct device_node *np,
struct device_node *isa_brg)
{
@@ -302,6 +330,17 @@ void __init find_legacy_serial_ports(voi
of_node_put(isa);
}
+ /* First fill our array with tsi-bridge ports */
+ for (np = NULL; (np = of_find_compatible_node(np, "serial", "ns16550")) != NULL;) {
+ struct device_node *tsi = of_get_parent(np);
+ if (tsi && !strcmp(tsi->type, "tsi-bridge")) {
+ index = add_legacy_tsi_port(np, np);
+ if (index >= 0 && np == stdout)
+ legacy_serial_console = index;
+ }
+ of_node_put(tsi);
+ }
+
#ifdef CONFIG_PCI
/* Next, try to locate PCI ports */
for (np = NULL; (np = of_find_all_nodes(np));) {
diff --git a/arch/powerpc/platforms/74xx/Makefile b/arch/powerpc/platforms/74xx/Makefile
new file mode 100644
index 0000000..ed5648c
--- /dev/null
+++ b/arch/powerpc/platforms/74xx/Makefile
@@ -0,0 +1,4 @@
+#
+# Makefile for the PowerPC 74xx linux kernel.
+#
+obj-$(CONFIG_MPC7448HPC2) += mpc7448_hpc2.o pci.o misc.o
diff --git a/arch/powerpc/platforms/74xx/misc.c b/arch/powerpc/platforms/74xx/misc.c
new file mode 100644
index 0000000..6228875
--- /dev/null
+++ b/arch/powerpc/platforms/74xx/misc.c
@@ -0,0 +1,38 @@
+
+/*
+ * MPC74xx generic code.
+ *
+ * Maintained by Roy Zang
+ *
+ * Copyright 2006 Freescale Semiconductor Inc.
+ *
+ * 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.
+ */
+#include <linux/irq.h>
+#include <linux/module.h>
+#include <asm/irq.h>
+extern void _nmask_and_or_msr(unsigned long nmask, unsigned long or_val);
+
+void mpc7448_hpc2_restart(char *cmd)
+{
+ local_irq_disable();
+
+ /* Set exception prefix high - to the firmware */
+ _nmask_and_or_msr(0, MSR_IP);
+
+ for (;;) ; /* Spin until reset happens */
+}
+
+void mpc7448_hpc2_power_off(void)
+{
+ local_irq_disable();
+ for (;;) ; /* No way to shut power off with software */
+}
+
+void mpc7448_hpc2_halt(void)
+{
+ mpc7448_hpc2_power_off();
+}
diff --git a/arch/powerpc/platforms/74xx/mpc7448_hpc2.c b/arch/powerpc/platforms/74xx/mpc7448_hpc2.c
new file mode 100644
index 0000000..b060ae1
--- /dev/null
+++ b/arch/powerpc/platforms/74xx/mpc7448_hpc2.c
@@ -0,0 +1,257 @@
+/*
+ * mpc7448_hpc2.c
+ *
+ * Board setup routines for the Freescale Taiga platform
+ *
+ * Author: Jacob Pan
+ * jacob.pan@freescale.com
+ * Author: Xianghua Xiao
+ * updated for taiga board from emulation platform 2005.5
+ * x.xiao@freescale.com
+ * Maintainer: Roy Zang <tie-fei.zang@freescale.com>
+ *
+ * Copyright 2004-2006 Freescale Semiconductor, Inc.
+ *
+ * This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+
+#include <linux/config.h>
+#include <linux/stddef.h>
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/kdev_t.h>
+#include <linux/console.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/ide.h>
+#include <linux/seq_file.h>
+#include <linux/root_dev.h>
+#include <linux/serial.h>
+#include <linux/tty.h>
+#include <linux/serial_core.h>
+
+#include <asm/system.h>
+#include <asm/time.h>
+#include <asm/machdep.h>
+#include <asm/prom.h>
+#include <asm/udbg.h>
+#include <asm/tsi108.h>
+#include <asm/pci-bridge.h>
+#include <asm/reg.h>
+#include <mm/mmu_decl.h>
+#include "mpc7448_hpc2.h"
+#include <asm/tsi108_pic.h>
+
+#ifndef CONFIG_PCI
+isa_io_base = MPC7448_HPC2_ISA_IO_BASE;
+isa_mem_base = MPC7448_HPC2_ISA_MEM_BASE;
+pci_dram_offset = MPC7448_HPC2_PCI_MEM_OFFSET;
+#endif
+
+extern int add_bridge(struct device_node *dev);
+extern void mpc7448_hpc2_restart(char *cmd);
+extern void mpc7448_hpc2_power_off(void);
+extern void mpc7448_hpc2_halt(void);
+
+#ifdef TSI108_ETH
+hw_info hw_info_table[TSI108_ETH_MAX_PORTS + 1] = {
+ {TSI108_CSR_ADDR_PHYS + TSI108_ETH_OFFSET,
+ TSI108_CSR_ADDR_PHYS + TSI108_ETH_OFFSET,
+ TSI108_PHY0_ADDR, IRQ_TSI108_GIGE0},
+
+ {TSI108_CSR_ADDR_PHYS + TSI108_ETH_OFFSET + 0x400,
+ TSI108_CSR_ADDR_PHYS + TSI108_ETH_OFFSET,
+ TSI108_PHY1_ADDR, IRQ_TSI108_GIGE1},
+
+ {TBL_END, TBL_END, TBL_END, TBL_END}
+};
+#endif
+
+/*
+ * Define all of the IRQ senses and polarities. Taken from the
+ * mpc7448hpc manual.
+ * Note: Likely, this table and the following function should be
+ * obtained and derived from the OF Device Tree.
+ */
+
+static u_char mpc7448_hpc2_pic_initsenses[] __initdata = {
+ (IRQ_SENSE_LEVEL | IRQ_POLARITY_NEGATIVE), /* INT[0] XINT0 from FPGA */
+ (IRQ_SENSE_LEVEL | IRQ_POLARITY_NEGATIVE), /* INT[1] XINT1 from FPGA */
+ (IRQ_SENSE_LEVEL | IRQ_POLARITY_NEGATIVE), /* INT[2] PHY_INT from both GIGE */
+ (IRQ_SENSE_LEVEL | IRQ_POLARITY_NEGATIVE), /* INT[3] RESERVED */
+};
+
+/*
+ * mpc7448hpc2 PCI interrupt routing. all PCI interrupt comes from
+ * external PCI source at 23. need to program pci interrupt control registers
+ * to route per slot IRQs.
+ */
+
+static inline int
+mpc7448_hpc2_map_irq(struct pci_dev *dev, unsigned char idsel,
+ unsigned char pin)
+{
+ static char pci_irq_table[][4] =
+ /*
+ * PCI IDSEL/INTPIN->INTLINE
+ * A B C D
+ */
+ {
+ {IRQ_PCI_INTA, IRQ_PCI_INTB, IRQ_PCI_INTC, IRQ_PCI_INTD}, /* A SLOT 1 IDSEL 17 */
+ {IRQ_PCI_INTB, IRQ_PCI_INTC, IRQ_PCI_INTD, IRQ_PCI_INTA}, /* B SLOT 2 IDSEL 18 */
+ {IRQ_PCI_INTC, IRQ_PCI_INTD, IRQ_PCI_INTA, IRQ_PCI_INTB}, /* C SATA IDSEL 19 */
+ {IRQ_PCI_INTD, IRQ_PCI_INTA, IRQ_PCI_INTB, IRQ_PCI_INTC}, /* D USB IDSEL 20 */
+ };
+
+ const long min_idsel = 1, max_idsel = 4, irqs_per_slot = 4;
+ return PCI_IRQ_TABLE_LOOKUP;
+}
+
+int mpc7448_hpc2_exclude_device(u_char bus, u_char devfn)
+{
+ if (bus == 0 && PCI_SLOT(devfn) == 0)
+ return PCIBIOS_DEVICE_NOT_FOUND;
+ else
+ return PCIBIOS_SUCCESSFUL;
+}
+
+static __inline__ void mpc7448_hpc2_l2cr_prefetch_enable(void)
+{
+ unsigned long msscr0;
+ __asm__ __volatile__("mfspr %0, 0x3f6\n \
+ ori %0,%0,0x3\n \
+ sync \n \
+ mtspr 0x3f6,%0\n \
+ sync\n \
+ isync ":"=r"(msscr0));
+}
+
+static void __init mpc7448_hpc2_setup_arch(void)
+{
+ struct device_node *cpu;
+ struct device_node *np;
+ if (ppc_md.progress)
+ ppc_md.progress("mpc7448_hpc2_setup_arch():set_bridge", 0);
+
+ cpu = of_find_node_by_type(NULL, "cpu");
+ if (cpu != 0) {
+ unsigned int *fp;
+
+ fp = (int *)get_property(cpu, "clock-frequency", NULL);
+ if (fp != 0)
+ loops_per_jiffy = *fp / HZ;
+ else
+ loops_per_jiffy = 50000000 / HZ;
+ of_node_put(cpu);
+ }
+
+#ifdef CONFIG_ROOT_NFS
+ ROOT_DEV = Root_NFS;
+#else
+ ROOT_DEV = Root_HDA1;
+#endif
+
+#ifdef CONFIG_BLK_DEV_INITRD
+ ROOT_DEV = Root_RAM0;
+#endif
+
+ /* setup PCI host bridge */
+
+#ifdef CONFIG_PCI
+
+ for (np = NULL; (np = of_find_node_by_type(np, "pci")) != NULL;)
+ add_bridge(np);
+
+ ppc_md.pci_swizzle = common_swizzle;
+ ppc_md.pci_map_irq = mpc7448_hpc2_map_irq;
+ ppc_md.pci_exclude_device = mpc7448_hpc2_exclude_device;
+ if (ppc_md.progress)
+ ppc_md.progress("tsi108: resources set", 0x100);
+#endif
+
+#ifdef CONFIG_DUMMY_CONSOLE
+ conswitchp = &dummy_con;
+#endif
+
+ printk(KERN_INFO "MPC7448HPC2 (TAIGA) Platform\n");
+ printk(KERN_INFO
+ "Jointly ported by Freescale and Tundra Semiconductor\n");
+ printk(KERN_INFO
+ "Enabling L2 cache then enabling the HID0 prefetch engine.\n");
+ _set_L2CR(L2CR_L2E);
+ mpc7448_hpc2_l2cr_prefetch_enable();
+}
+
+/*
+ * Interrupt setup and service. Interrrupts on the mpc7448_hpc2 come
+ * from the four external INT pins, PCI interrupts are routed via
+ * PCI interrupt control registers, it generates internal IRQ23
+ *
+ * Interrupt routing on the Taiga Board:
+ * TSI108:PB_INT[0] -> CPU0:INT#
+ * TSI108:PB_INT[1] -> CPU0:MCP#
+ * TSI108:PB_INT[2] -> N/C
+ * TSI108:PB_INT[3] -> N/C
+ */
+static void __init mpc7448_hpc2_init_IRQ(void)
+{
+
+ tsi108_pic_init(mpc7448_hpc2_pic_initsenses);
+
+ /* Configure MPIC outputs to CPU0 */
+ tsi108_pic_set_output(0, IRQ_SENSE_EDGE, IRQ_POLARITY_NEGATIVE);
+}
+
+static void __init mpc7448_hpc2_map_io(void)
+{
+ /* PCI IO mapping */
+ io_block_mapping(MPC7448_HPC2_PCI_IO_BASE_VIRT,
+ MPC7448_HPC2_PCI_IO_BASE_PHYS, 0x00800000, _PAGE_IO);
+ /* Tsi108 CSR mapping */
+ io_block_mapping(TSI108_CSR_ADDR_VIRT, TSI108_CSR_ADDR_PHYS,
+ 0x100000, _PAGE_IO);
+
+ /* PCI Config mapping */
+ io_block_mapping(MPC7448_HPC2_PCI_CFG_BASE_VIRT,
+ MPC7448_HPC2_PCI_CFG_BASE_PHYS,
+ MPC7448_HPC2_PCI_CFG_SIZE, _PAGE_IO);
+
+ tsi108_pci_cfg_base = MPC7448_HPC2_PCI_CFG_BASE_VIRT;
+ /* NVRAM mapping */
+ io_block_mapping(MPC7448_HPC2_NVRAM_BASE_ADDR,
+ MPC7448_HPC2_NVRAM_BASE_ADDR, MPC7448_HPC2_NVRAM_SIZE,
+ _PAGE_IO);
+}
+
+void mpc7448_hpc2_show_cpuinfo(struct seq_file *m)
+{
+ seq_printf(m, "vendor\t\t: Freescale Semiconductor\n");
+ seq_printf(m, "machine\t\t: MPC7448hpc2\n");
+}
+
+/*
+ * Called very early, device-tree isn't unflattened
+ */
+static int __init mpc7448_hpc2_probe(void)
+{
+ /* We always match for now, eventually we should look at the flat
+ dev tree to ensure this is the board we are suppose to run on
+ */
+ return 1;
+}
+
+define_machine(mpc7448_hpc2){
+ .name = "MPC7448 HPC2",
+ .probe = mpc7448_hpc2_probe,
+ .setup_arch = mpc7448_hpc2_setup_arch,
+ .init_IRQ = mpc7448_hpc2_init_IRQ,
+ .show_cpuinfo = mpc7448_hpc2_show_cpuinfo,
+ .get_irq = tsi108_pic_get_irq,
+ .restart = mpc7448_hpc2_restart,
+ .calibrate_decr = generic_calibrate_decr,
+ .setup_io_mappings = mpc7448_hpc2_map_io,
+ .progress = udbg_progress,
+};
diff --git a/arch/powerpc/platforms/74xx/mpc7448_hpc2.h b/arch/powerpc/platforms/74xx/mpc7448_hpc2.h
new file mode 100644
index 0000000..8833520
--- /dev/null
+++ b/arch/powerpc/platforms/74xx/mpc7448_hpc2.h
@@ -0,0 +1,80 @@
+/*
+ * mpc7448_hpc2.h
+ *
+ * Definitions for Freescale MPC7448_HPC2 platform
+ *
+ * Author: Jacob Pan
+ * jacob.pan@freescale.com
+ * Maintainer: Roy Zang <roy.zang@freescale.com>
+ *
+ * 2006 (c) Freescale Semiconductor, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+
+#ifndef __PPC_PLATFORMS_MPC7448_HPC2_H
+#define __PPC_PLATFORMS_MPC7448_HPC2_H
+
+#include <asm/ppcboot.h>
+
+/* Ethernet defines
+ */
+#define TSI108_ETH
+#define TSI108_ETH_MAX_PORTS (2) /* TSI108 Ethernet block has 2 ports */
+#define TSI108_PHY0_ADDR (8) /* PHY address for GIGE port 0 */
+#define TSI108_PHY1_ADDR (9) /* PHY address for GIGE port 1 */
+
+/* Base Addresses for the PCI bus
+ * HOST_PCI initiator (outbound) window to PCI bus
+ */
+#define MPC7448_HPC2_PCI_MEM_OFFSET (0x00000000)
+#define MPC7448_HPC2_PCI_IO_BASE_PHYS (0xfa000000)
+#define MPC7448_HPC2_PCI_IO_BASE_VIRT (MPC7448_HPC2_PCI_IO_BASE_PHYS)
+#define MPC7448_HPC2_PCI_MEM_BASE (0xe0000000)
+#define MPC7448_HPC2_ISA_IO_BASE (0x00000000)
+#define MPC7448_HPC2_ISA_MEM_BASE (0x00000000)
+
+#define MPC7448_HPC2_PCI_CFG_BASE_PHYS (0xfb000000)
+#define MPC7448_HPC2_PCI_CFG_BASE_VIRT (MPC7448_HPC2_PCI_CFG_BASE_PHYS)
+#define MPC7448_HPC2_PCI_CFG_SIZE (0x01000000)
+
+#define MPC7448_HPC2_PCI_MEM_START (0xE0000000)
+#define MPC7448_HPC2_PCI_MEM_END (0xF9FFFFFF)
+
+#define MPC7448_HPC2_PCI_IO_START (0xFA000000)
+#define MPC7448_HPC2_PCI_IO_END (0xFA00FFFF)
+
+#define MPC7448_HPC2_PCI_CFG_OFFSET (0xFB000000)
+#define MPC7448_HPC2_PCI_IO_OFFSET (0xFA000000)
+
+#define MPC7448_HPC2_PCI_MEM32_OFFSET 0x00000000 /* PCI MEM32 space offset within
+ the PCI window */
+#define MPC7448_HPC2_PCI_PFM_OFFSET 0x10000000 /* PCI PFM1 space offset within
+ the PCI window */
+
+/* Memory-mapped CIU resources (CPU view)
+ * The memory map is set by initialization code in monitor
+ */
+
+#define TSI108_CSR_ADDR_PHYS (0xC0000000) /* Physical Tsi108 CSR Base Address */
+#define TSI108_CSR_ADDR_VIRT (0xF0000000) /* Virtual Tsi108 CSR Base Address */
+
+#define FLASH_BASE_ADDR (0xFF000000) /* Boot FLASH Base Address */
+
+#define SDRAM_BASE_ADDR 0x00000000 /* SDRAM base address */
+#define BOARD_SDRAM_SIZE 0x20000000 /* Default value: 512MB */
+
+/* Size of SDRAM space reserved for user space. This limits space available
+ * for dynamic allocation.
+ */
+#define USER_RESERVED_MEM (BOARD_SDRAM_SIZE - 0x02000000)
+
+#define USER_RESERVED_BASE \
+ (SDRAM_BASE_ADDR + (BOARD_SDRAM_SIZE - USER_RESERVED_MEM)
+
+/* Memory mapped NVRAM/RTC */
+#define MPC7448_HPC2_NVRAM_BASE_ADDR (0xFC000000)
+#define MPC7448_HPC2_NVRAM_SIZE (0x8000)
+
+#endif /* __PPC_PLATFORMS_MPC7448_HPC2_H */
diff --git a/arch/powerpc/platforms/74xx/pci.c b/arch/powerpc/platforms/74xx/pci.c
new file mode 100644
index 0000000..fd822ed
--- /dev/null
+++ b/arch/powerpc/platforms/74xx/pci.c
@@ -0,0 +1,103 @@
+/*
+ * TSI 108 PCI hose setup up code
+ *
+ * Maintained by Roy Zang
+ *
+ * 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.
+ */
+
+#include <linux/config.h>
+#include <linux/stddef.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/irq.h>
+#include <linux/module.h>
+
+#include <asm/system.h>
+#include <asm/atomic.h>
+#include <asm/io.h>
+#include <asm/pci-bridge.h>
+#include <asm/prom.h>
+#include <asm/tsi108.h>
+#include "mpc7448_hpc2.h"
+
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(x...) printk(x)
+#else
+#define DBG(x...)
+#endif
+
+extern int tsi108_direct_write_config(struct pci_bus *bus, unsigned int devfn,
+ int offset, int len, u32 val);
+extern int tsi108_direct_read_config(struct pci_bus *bus, unsigned int devfn,
+ int offset, int len, u32 * val);
+
+void tsi108_clear_pci_error(u32 pci_cfg_base);
+
+extern int
+tsi108_read_config(int bus, unsigned int devfn, int offset, int len, u32 * val);
+#ifdef CONFIG_PCI
+static struct pci_ops direct_pci_ops = {
+ tsi108_direct_read_config,
+ tsi108_direct_write_config
+};
+
+void tsi108_clear_pci_cfg_error(void)
+{
+ tsi108_clear_pci_error(MPC7448_HPC2_PCI_CFG_BASE_PHYS);
+}
+
+int __init add_bridge(struct device_node *dev)
+{
+ int len;
+ struct pci_controller *hose;
+ struct resource rsrc;
+ int *bus_range;
+ int primary = 0, has_address = 0;
+
+ DBG("TSI_PCI: %s tsi108_pci_cfg_base=0x%x\n", __FUNCTION__,
+ tsi108_pci_cfg_base);
+
+ /* Fetch host bridge registers address */
+ has_address = (of_address_to_resource(dev, 0, &rsrc) == 0);
+
+ /* Get bus range if any */
+ bus_range = (int *)get_property(dev, "bus-range", &len);
+ if (bus_range == NULL || len < 2 * sizeof(int)) {
+ printk(KERN_WARNING "Can't get bus-range for %s, assume"
+ " bus 0\n", dev->full_name);
+ }
+
+ hose = pcibios_alloc_controller();
+
+ if (!hose) {
+ printk("PCI Host bridge init failed\n");
+ return -ENOMEM;
+ }
+ hose->arch_data = dev;
+ hose->set_cfg_type = 1;
+
+ hose->first_busno = bus_range ? bus_range[0] : 0;
+ hose->last_busno = bus_range ? bus_range[1] : 0xff;
+
+ (hose)->ops = &direct_pci_ops;
+
+ printk(KERN_INFO "Found tsi108 PCI host bridge at 0x%08lx. "
+ "Firmware bus number: %d->%d\n",
+ rsrc.start, hose->first_busno, hose->last_busno);
+
+ /* Interpret the "ranges" property */
+ /* This also maps the I/O region and sets isa_io/mem_base */
+ pci_process_bridge_OF_ranges(hose, dev, primary);
+ return 0;
+
+}
+#endif
diff --git a/arch/powerpc/platforms/Makefile b/arch/powerpc/platforms/Makefile
index c4f6b0d..71ca0e0 100644
--- a/arch/powerpc/platforms/Makefile
+++ b/arch/powerpc/platforms/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_PPC_CHRP) += chrp/
obj-$(CONFIG_4xx) += 4xx/
obj-$(CONFIG_PPC_83xx) += 83xx/
obj-$(CONFIG_PPC_85xx) += 85xx/
+obj-$(CONFIG_74xx) += 74xx/
obj-$(CONFIG_PPC_PSERIES) += pseries/
obj-$(CONFIG_PPC_ISERIES) += iseries/
obj-$(CONFIG_PPC_MAPLE) += maple/
--
1.3.0
^ permalink raw reply related
* [PATCH/2.6.17-rc4 2/10] Powerpc: Add Tundra Semiconductor tsi108 macro define
From: Zang Roy-r61911 @ 2006-05-17 10:13 UTC (permalink / raw)
To: Paul Mackerras; +Cc: linuxppc-dev list, Alexandre.Bounine, Yang Xin-Xin-r48390
Add Tundra Semiconductor tsi108 host bridge common and irq macro define
Signed-off-by: Alexandre Bounine <alexandreb@tundra.com>
Signed-off-by: Roy Zang <tie-fei.zang@freescale.com>
---
include/asm-powerpc/tsi108.h | 106 +++++++++++++++++++++++++++++++
include/asm-powerpc/tsi108_irq.h | 131 ++++++++++++++++++++++++++++++++++++++
2 files changed, 237 insertions(+), 0 deletions(-)
create mode 100644 include/asm-powerpc/tsi108.h
create mode 100644 include/asm-powerpc/tsi108_irq.h
d7b3cf7ba073991ce7c2fac268d21f8bd43d6962
diff --git a/include/asm-powerpc/tsi108.h b/include/asm-powerpc/tsi108.h
new file mode 100644
index 0000000..ed9ec36
--- /dev/null
+++ b/include/asm-powerpc/tsi108.h
@@ -0,0 +1,106 @@
+/*
+ * include/asm-ppc/tsi108.h
+ *
+ * common routine and memory layout for Tundra TSI108(Grendel) host bridge
+ * memory controller.
+ *
+ * Author: Jacob Pan (jacob.pan@freescale.com)
+ * Alex Bounine (alexandreb@tundra.com)
+ * 2004 (c) Freescale Semiconductor Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ */
+#ifndef __PPC_KERNEL_TSI108_H
+#define __PPC_KERNEL_TSI108_H
+
+#include <asm/pci-bridge.h>
+
+/* Size of entire register space */
+#define TSI108_REG_SIZE (0x10000)
+
+/* Sizes of register spaces for individual blocks */
+#define TSI108_HLP_SIZE 0x1000
+#define TSI108_PCI_SIZE 0x1000
+#define TSI108_CLK_SIZE 0x1000
+#define TSI108_PB_SIZE 0x1000
+#define TSI108_SD_SIZE 0x1000
+#define TSI108_DMA_SIZE 0x1000
+#define TSI108_ETH_SIZE 0x1000
+#define TSI108_I2C_SIZE 0x400
+#define TSI108_MPIC_SIZE 0x400
+#define TSI108_UART0_SIZE 0x200
+#define TSI108_GPIO_SIZE 0x200
+#define TSI108_UART1_SIZE 0x200
+
+/* Offsets within Tsi108(A) CSR space for individual blocks */
+#define TSI108_HLP_OFFSET 0x0000
+#define TSI108_PCI_OFFSET 0x1000
+#define TSI108_CLK_OFFSET 0x2000
+#define TSI108_PB_OFFSET 0x3000
+#define TSI108_SD_OFFSET 0x4000
+#define TSI108_DMA_OFFSET 0x5000
+#define TSI108_ETH_OFFSET 0x6000
+#define TSI108_I2C_OFFSET 0x7000
+#define TSI108_MPIC_OFFSET 0x7400
+#define TSI108_UART0_OFFSET 0x7800
+#define TSI108_GPIO_OFFSET 0x7A00
+#define TSI108_UART1_OFFSET 0x7C00
+
+/* Tsi108 registers used by common code components */
+#define TSI108_PCI_CSR (0x004)
+#define TSI108_PCI_IRP_CFG_CTL (0x180)
+#define TSI108_PCI_IRP_STAT (0x184)
+#define TSI108_PCI_IRP_ENABLE (0x188)
+#define TSI108_PCI_IRP_INTAD (0x18C)
+
+#define TSI108_PCI_IRP_STAT_P_INT (0x00400000)
+
+#define TSI108_CG_PWRUP_STATUS (0x234)
+
+#define TSI108_PB_ISR (0x00C)
+#define TSI108_PB_ERRCS (0x404)
+#define TSI108_PB_AERR (0x408)
+
+#define TSI108_PB_ERRCS_ES (1 << 1)
+#define TSI108_PB_ISR_PBS_RD_ERR (1 << 8)
+
+#define TBL_END 0
+
+/* Global variables */
+
+extern u32 tsi108_pci_cfg_base;
+/* Exported functions */
+
+extern int tsi108_bridge_init(struct pci_controller *hose, uint phys_csr_base);
+extern unsigned long tsi108_get_mem_size(void);
+extern unsigned long tsi108_get_cpu_clk(void);
+extern unsigned long tsi108_get_sdc_clk(void);
+extern int tsi108_direct_write_config(struct pci_bus *bus, unsigned int devfn,
+ int offset, int len, u32 val);
+extern int tsi108_direct_read_config(struct pci_bus *bus, unsigned int devfn,
+ int offset, int len, u32 * val);
+extern void tsi108_clear_pci_error(u32 pci_cfg_base);
+
+extern phys_addr_t get_csrbase(void);
+
+typedef struct {
+ u32 regs; /* hw registers base address */
+ u32 phyregs; /* phy registers base address */
+ u16 phy; /* phy address */
+ u16 irq_num; /* irq number */
+} hw_info;
+
+extern u32 get_vir_csrbase(void);
+
+extern inline u32 tsi108_read_reg(u32 reg_offset)
+{
+ return in_be32((volatile u32 *)(get_vir_csrbase() + reg_offset));
+}
+
+extern inline void tsi108_write_reg(u32 reg_offset, u32 val)
+{
+ out_be32((volatile u32 *)(get_vir_csrbase() + reg_offset), val);
+}
+
+#endif /* __PPC_KERNEL_TSI108_H */
diff --git a/include/asm-powerpc/tsi108_irq.h b/include/asm-powerpc/tsi108_irq.h
new file mode 100644
index 0000000..2f7e8d8
--- /dev/null
+++ b/include/asm-powerpc/tsi108_irq.h
@@ -0,0 +1,131 @@
+/*
+ * (C) Copyright 2005 Tundra Semiconductor Corp.
+ * Alex Bounine, <alexandreb@tundra.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
+ */
+
+/*
+ * asm-ppc/tsi108_irq.h - definitions for interrupt controller
+ * initialization and external interrupt demultiplexing on TSI108EMU/SVB boards.
+ */
+
+#ifndef _ASM_PPC_TSI108_IRQ_H
+#define _ASM_PPC_TSI108_IRQ_H
+
+/*
+ * Tsi108 interrupts
+ */
+#ifndef TSI108_IRQ_BASE
+#define TSI108_IRQ_BASE 0
+#endif
+
+#define TSI108_IRQ(x) (TSI108_IRQ_BASE + (x))
+
+#define TSI108_MAX_VECTORS (36 + 4) /* 36 sources + PCI INT demux */
+#define MAX_TASK_PRIO 0xF
+
+#define TSI108_IRQ_SPURIOUS (TSI108_MAX_VECTORS)
+
+#define DEFAULT_PRIO_LVL 10 /* initial priority level */
+
+/* Interrupt vectors assignment to external and internal
+ * sources of requests. */
+
+/* EXTERNAL INTERRUPT SOURCES */
+
+#define IRQ_TSI108_EXT_INT0 TSI108_IRQ(0) /* External Source at INT[0] */
+#define IRQ_TSI108_EXT_INT1 TSI108_IRQ(1) /* External Source at INT[1] */
+#define IRQ_TSI108_EXT_INT2 TSI108_IRQ(2) /* External Source at INT[2] */
+#define IRQ_TSI108_EXT_INT3 TSI108_IRQ(3) /* External Source at INT[3] */
+
+/* INTERNAL INTERRUPT SOURCES */
+
+#define IRQ_TSI108_RESERVED0 TSI108_IRQ(4) /* Reserved IRQ */
+#define IRQ_TSI108_RESERVED1 TSI108_IRQ(5) /* Reserved IRQ */
+#define IRQ_TSI108_RESERVED2 TSI108_IRQ(6) /* Reserved IRQ */
+#define IRQ_TSI108_RESERVED3 TSI108_IRQ(7) /* Reserved IRQ */
+#define IRQ_TSI108_DMA0 TSI108_IRQ(8) /* DMA0 */
+#define IRQ_TSI108_DMA1 TSI108_IRQ(9) /* DMA1 */
+#define IRQ_TSI108_DMA2 TSI108_IRQ(10) /* DMA2 */
+#define IRQ_TSI108_DMA3 TSI108_IRQ(11) /* DMA3 */
+#define IRQ_TSI108_UART0 TSI108_IRQ(12) /* UART0 */
+#define IRQ_TSI108_UART1 TSI108_IRQ(13) /* UART1 */
+#define IRQ_TSI108_I2C TSI108_IRQ(14) /* I2C */
+#define IRQ_TSI108_GPIO TSI108_IRQ(15) /* GPIO */
+#define IRQ_TSI108_GIGE0 TSI108_IRQ(16) /* GIGE0 */
+#define IRQ_TSI108_GIGE1 TSI108_IRQ(17) /* GIGE1 */
+#define IRQ_TSI108_RESERVED4 TSI108_IRQ(18) /* Reserved IRQ */
+#define IRQ_TSI108_HLP TSI108_IRQ(19) /* HLP */
+#define IRQ_TSI108_SDRAM TSI108_IRQ(20) /* SDC */
+#define IRQ_TSI108_PROC_IF TSI108_IRQ(21) /* Processor IF */
+#define IRQ_TSI108_RESERVED5 TSI108_IRQ(22) /* Reserved IRQ */
+#define IRQ_TSI108_PCI TSI108_IRQ(23) /* PCI/X block */
+
+#define IRQ_TSI108_MBOX0 TSI108_IRQ(24) /* Mailbox 0 register */
+#define IRQ_TSI108_MBOX1 TSI108_IRQ(25) /* Mailbox 1 register */
+#define IRQ_TSI108_MBOX2 TSI108_IRQ(26) /* Mailbox 2 register */
+#define IRQ_TSI108_MBOX3 TSI108_IRQ(27) /* Mailbox 3 register */
+
+#define IRQ_TSI108_DBELL0 TSI108_IRQ(28) /* Doorbell 0 */
+#define IRQ_TSI108_DBELL1 TSI108_IRQ(29) /* Doorbell 1 */
+#define IRQ_TSI108_DBELL2 TSI108_IRQ(30) /* Doorbell 2 */
+#define IRQ_TSI108_DBELL3 TSI108_IRQ(31) /* Doorbell 3 */
+
+#define IRQ_TSI108_TIMER0 TSI108_IRQ(32) /* Global Timer 0 */
+#define IRQ_TSI108_TIMER1 TSI108_IRQ(33) /* Global Timer 1 */
+#define IRQ_TSI108_TIMER2 TSI108_IRQ(34) /* Global Timer 2 */
+#define IRQ_TSI108_TIMER3 TSI108_IRQ(35) /* Global Timer 3 */
+
+/*
+ * PCI bus INTA# - INTD# lines demultiplexor
+ */
+#define IRQ_PCI_INTAD_BASE TSI108_IRQ(36)
+#define IRQ_PCI_INTA (IRQ_PCI_INTAD_BASE + 0)
+#define IRQ_PCI_INTB (IRQ_PCI_INTAD_BASE + 1)
+#define IRQ_PCI_INTC (IRQ_PCI_INTAD_BASE + 2)
+#define IRQ_PCI_INTD (IRQ_PCI_INTAD_BASE + 3)
+#define NUM_PCI_IRQS (4)
+
+/* number of entries in vector dispatch table */
+#define IRQ_TSI108_TAB_SIZE (TSI108_MAX_VECTORS + 1)
+
+/* Mapping of MPIC outputs to processors' interrupt pins */
+
+#define IDIR_INT_OUT0 0x1
+#define IDIR_INT_OUT1 0x2
+#define IDIR_INT_OUT2 0x4
+#define IDIR_INT_OUT3 0x8
+
+/*---------------------------------------------------------------
+ * IRQ line configuration parameters */
+
+/* Interrupt delivery modes */
+typedef enum {
+ TSI108_IRQ_DIRECTED,
+ TSI108_IRQ_DISTRIBUTED,
+} TSI108_IRQ_MODE;
+
+/*--------------------------------------------------------------- */
+/* Error codes */
+
+#define MPIC_OK 0
+#define MPIC_ERROR 1
+
+#endif /* _ASM_PPC_TSI108_IRQ_H */
--
1.3.0
^ permalink raw reply related
* [PATCH/2.6.17-rc4 3/10] Powerpc: Add tsi108 common function
From: Zang Roy-r61911 @ 2006-05-17 10:14 UTC (permalink / raw)
To: Paul Mackerras; +Cc: linuxppc-dev list, Alexandre.Bounine, Yang Xin-Xin-r48390
Add Tundra Semiconductor tsi108 host bridge common function support.
Signed-off-by: Alexandre Bounine <alexandreb@tundra.com>
Signed-off-by: Roy Zang <tie-fei.zang@freescale.com>
---
arch/powerpc/sysdev/Makefile | 1
arch/powerpc/sysdev/tsi108_common.c | 224 +++++++++++++++++++++++++++++++++++
2 files changed, 225 insertions(+), 0 deletions(-)
create mode 100644 arch/powerpc/sysdev/tsi108_common.c
ab3b477b5924d2c9cbf6ba3ae4d95fe333c9bad9
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index 4c2b356..8c0afb7 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -8,3 +8,4 @@ obj-$(CONFIG_U3_DART) += dart_iommu.o
obj-$(CONFIG_MMIO_NVRAM) += mmio_nvram.o
obj-$(CONFIG_PPC_83xx) += ipic.o
obj-$(CONFIG_FSL_SOC) += fsl_soc.o
+obj-$(CONFIG_TSI108_BRIDGE) += tsi108_common.o tsi108_pic.o
diff --git a/arch/powerpc/sysdev/tsi108_common.c b/arch/powerpc/sysdev/tsi108_common.c
new file mode 100644
index 0000000..3c55f99
--- /dev/null
+++ b/arch/powerpc/sysdev/tsi108_common.c
@@ -0,0 +1,224 @@
+/*
+ * arch/ppc/syslib/tsi108_common.c
+ *
+ * Common routines for Tundra Semiconductor TSI108 host bridge.
+ *
+ * 2004-2005 (c) Tundra Semiconductor Corp.
+ * Author: Alex Bounine (alexandreb@tundra.com)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+
+#include <asm/byteorder.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include <asm/machdep.h>
+#include <asm/pci-bridge.h>
+#include <asm/tsi108.h>
+#include <asm/prom.h>
+
+u32 tsi108_pci_cfg_base;
+
+#undef TSI108_PCI_DEBUG
+
+#define tsi_mk_config_addr(bus, devfunc, offset) \
+ (((bus)<<16) | ((devfunc)<<8) | (offset & 0xfc)| tsi108_pci_cfg_base)
+
+static phys_addr_t tsi108_csr_base = -1;
+
+phys_addr_t get_csrbase(void)
+{
+ struct device_node *tsi;
+
+ if (tsi108_csr_base != -1)
+ return tsi108_csr_base;
+
+ tsi = of_find_node_by_type(NULL, "tsi-bridge");
+ if (tsi) {
+ unsigned int size;
+ void *prop = get_property(tsi, "reg", &size);
+ tsi108_csr_base = of_translate_address(tsi, prop);
+ of_node_put(tsi);
+ };
+ return tsi108_csr_base;
+}
+
+u32 get_vir_csrbase(void)
+{
+ return (u32) (ioremap(get_csrbase(), 0x10000));
+}
+
+EXPORT_SYMBOL(get_csrbase);
+EXPORT_SYMBOL(get_vir_csrbase);
+
+/*
+ * Prosessor Bus Clock (in MHz) defined by CG_PB_SELECT
+ * (based on recommended Tsi108 reference clock 33MHz)
+ */
+static int pb_clk_sel[8] = { 0, 0, 183, 100, 133, 167, 200, 233 };
+
+/*
+ * SDRAM Clock (in MHz) defined by CG_SD_SELECT
+ * (based on recommended Tsi108 reference clock 33MHz)
+ */
+static int sd_clk_sel[2][8] = {
+ {0, 0, 183, 100, 133, 167, 200, 233}, /* SYNC */
+ {0, 0, 0, 0, 133, 160, 200, 0} /* ASYNC */
+};
+
+int
+tsi108_direct_write_config(struct pci_bus *bus, unsigned int devfunc,
+ int offset, int len, u32 val)
+{
+ volatile unsigned char *cfg_addr;
+
+ cfg_addr = (unsigned char *)(tsi_mk_config_addr(bus->number,
+ devfunc, offset) |
+ (offset & 0x03));
+
+#ifdef TSI108_PCI_DEBUG
+ printk("PCI CFG write : ");
+ printk("%d:0x%x:0x%x ", bus->number, devfunc, offset);
+ printk("%d ADDR=0x%08x ", len, (uint) cfg_addr);
+ printk("data = 0x%08x\n", val);
+#endif
+
+ switch (len) {
+ case 1:
+ out_8((u8 *) cfg_addr, val);
+ break;
+ case 2:
+ out_le16((u16 *) cfg_addr, val);
+ break;
+ default:
+ out_le32((u32 *) cfg_addr, val);
+ break;
+ }
+
+ return PCIBIOS_SUCCESSFUL;
+}
+
+void tsi108_clear_pci_error(u32 pci_cfg_base)
+{
+ u32 err_stat, err_addr, pci_stat;
+
+ /*
+ * Quietly clear PB and PCI error flags set as result
+ * of PCI/X configuration read requests.
+ */
+
+ /* Read PB Error Log Registers */
+
+ err_stat = tsi108_read_reg(TSI108_PB_OFFSET + TSI108_PB_ERRCS);
+ err_addr = tsi108_read_reg(TSI108_PB_OFFSET + TSI108_PB_AERR);
+
+ if (err_stat & TSI108_PB_ERRCS_ES) {
+ /* Clear error flag */
+ tsi108_write_reg(TSI108_PB_OFFSET + TSI108_PB_ERRCS,
+ TSI108_PB_ERRCS_ES);
+
+ /* Clear read error reported in PB_ISR */
+ tsi108_write_reg(TSI108_PB_OFFSET + TSI108_PB_ISR,
+ TSI108_PB_ISR_PBS_RD_ERR);
+
+ /* Clear PCI/X bus cfg errors if applicable */
+ if ((err_addr & 0xFF000000) == pci_cfg_base) {
+ pci_stat =
+ tsi108_read_reg(TSI108_PCI_OFFSET + TSI108_PCI_CSR);
+ tsi108_write_reg(TSI108_PCI_OFFSET + TSI108_PCI_CSR,
+ pci_stat);
+ }
+ }
+
+ return;
+}
+
+#define __tsi108_read_pci_config(x, addr, op) \
+ __asm__ __volatile__( \
+ " "op" %0,0,%1\n" \
+ "1: eieio\n" \
+ "2:\n" \
+ ".section .fixup,\"ax\"\n" \
+ "3: li %0,-1\n" \
+ " b 2b\n" \
+ ".section __ex_table,\"a\"\n" \
+ " .align 2\n" \
+ " .long 1b,3b\n" \
+ ".text" \
+ : "=r"(x) : "r"(addr))
+
+int
+tsi108_direct_read_config(struct pci_bus *bus, unsigned int devfn, int offset,
+ int len, u32 * val)
+{
+ volatile unsigned char *cfg_addr;
+ u32 temp;
+
+ cfg_addr = (unsigned char *)(tsi_mk_config_addr(bus->number,
+ devfn,
+ offset) | (offset &
+ 0x03));
+
+ switch (len) {
+ case 1:
+ __tsi108_read_pci_config(temp, cfg_addr, "lbzx");
+ break;
+ case 2:
+ __tsi108_read_pci_config(temp, cfg_addr, "lhbrx");
+ break;
+ default:
+ __tsi108_read_pci_config(temp, cfg_addr, "lwbrx");
+ break;
+ }
+
+ *val = temp;
+
+#ifdef TSI108_PCI_DEBUG
+ if ((0xFFFFFFFF != temp) && (0xFFFF != temp) && (0xFF != temp)) {
+ printk("PCI CFG read : ");
+ printk("%d:0x%x:0x%x ", bus->number, devfn, offset);
+ printk("%d ADDR=0x%08x ", len, (uint) cfg_addr);
+ printk("data = 0x%x\n", *val);
+ }
+#endif
+ return PCIBIOS_SUCCESSFUL;
+}
+
+unsigned long tsi108_get_cpu_clk(void)
+{
+ /* Detect PB clock freq. */
+ u32 i = tsi108_read_reg(TSI108_CLK_OFFSET + TSI108_CG_PWRUP_STATUS);
+
+ i = (i >> 16) & 0x07; /* Get PB PLL multiplier */
+ return (pb_clk_sel[i] * 1000000);
+}
+
+unsigned long tsi108_get_sdc_clk(void)
+{
+ u32 i, k;
+
+ /* Get SDC/PB clock freq. from CG settings */
+ i = tsi108_read_reg(TSI108_CLK_OFFSET + TSI108_CG_PWRUP_STATUS);
+ k = (i >> 16) & 0x07; /* Get PB PLL multiplier */
+ i = (i >> 20) & 0x07; /* Get SDC PLL multiplier */
+ k = (k == i) ? 0 : 1; /* sync/async configuration */
+ return (sd_clk_sel[k][i] * 1000000);
+}
--
1.3.0
^ permalink raw reply related
* [PATCH/2.6.17-rc4 4/10]Powerpc: Add tsi108 pic support
From: Zang Roy-r61911 @ 2006-05-17 10:14 UTC (permalink / raw)
To: Paul Mackerras; +Cc: linuxppc-dev list, Alexandre.Bounine, Yang Xin-Xin-r48390
Add Tundra Semiconductor tsi108 host bridge interrupt controller support.
Signed-off-by: Alexandre Bounine <alexandreb@tundra.com>
Signed-off-by: Roy Zang <tie-fei.zang@freescale.com>
---
arch/powerpc/sysdev/tsi108_pic.c | 813 ++++++++++++++++++++++++++++++++++++++
include/asm-powerpc/tsi108_pic.h | 232 +++++++++++
2 files changed, 1045 insertions(+), 0 deletions(-)
create mode 100644 arch/powerpc/sysdev/tsi108_pic.c
create mode 100644 include/asm-powerpc/tsi108_pic.h
7d23f6a0984cd54ca787f880a57067330900abe8
diff --git a/arch/powerpc/sysdev/tsi108_pic.c b/arch/powerpc/sysdev/tsi108_pic.c
new file mode 100644
index 0000000..bbca587
--- /dev/null
+++ b/arch/powerpc/sysdev/tsi108_pic.c
@@ -0,0 +1,813 @@
+/*
+ * (C) Copyright 2005 Tundra Semiconductor Corp.
+ * Alex Bounine, <alexandreb@tundra.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
+ */
+
+/*
+ * Tsi108 Interrupt Controller Handling
+ */
+
+#include <linux/config.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/sysdev.h>
+#include <asm/ptrace.h>
+#include <asm/signal.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/prom.h>
+#include <asm/sections.h>
+#include <asm/hardirq.h>
+#include <asm/machdep.h>
+
+#include <asm/tsi108.h>
+#include <asm/tsi108_irq.h>
+#include <asm/tsi108_pic.h>
+#undef DEBUG
+
+#ifdef DEBUG
+#define DBG(fmt...) do { printk(fmt); } while(0)
+#else
+#define DBG(fmt...) do { } while(0)
+#endif
+
+extern u32 get_vir_csrbase(void);
+extern u32 tsi108_read_reg(u32 reg_offset);
+extern void tsi108_write_reg(u32 reg_offset, u32 val);
+
+static phys_addr_t tsi108_pic_phy_addr;
+static u32 tsi108_pic_vir_addr;
+
+static int tsi108_pic_cascade_irq = -1;
+static int (*tsi108_pic_cascade_fn) (struct pt_regs *);
+
+/* Global Operations */
+static void tsi108_pic_set_task_priority(u_int pri);
+static void tsi108_pic_set_spurious(u_int vector);
+void tsi108_pic_mask_all(void);
+
+/* Timer Interrupts */
+static void tsi108_pic_inittimer(u_int timer, u_int pri, u_int vector);
+static void tsi108_pic_maptimer(u_int timer, u_int cpumask);
+
+/* Interrupt Sources */
+static void tsi108_pic_enable_irq(u_int irq);
+static void tsi108_pic_disable_irq(u_int irq);
+static void tsi108_pic_initirq(u_int irq, u_int pri, u_int vector, int polarity,
+ int is_level);
+static void tsi108_pic_mapirq(u_int irq, u_int cpumask, u_int keepmask);
+static void init_pci_source(void);
+static inline int get_pci_source(int vector);
+int tsi108_pic_set_irq_sense(int irq, int pol, int sense);
+
+/*
+ * tsi108_pic interface routines
+ */
+static void tsi108_pic_end_irq(unsigned int irq_nr);
+static void tsi108_pic_ack_irq(unsigned int irq_nr);
+void tsi108_pic_set_affinity(unsigned int irq_nr, unsigned long cpumask);
+
+static struct hw_interrupt_type tsi108_pic_irq = {
+ "tsi108_pic",
+ NULL,
+ NULL,
+ tsi108_pic_enable_irq,
+ tsi108_pic_disable_irq,
+ tsi108_pic_ack_irq,
+ tsi108_pic_end_irq,
+ NULL
+};
+
+static void tsi108_pci_irq_enable(u_int irq);
+static void tsi108_pci_irq_disable(u_int irq);
+static void tsi108_pci_irq_ack(u_int irq);
+static void tsi108_pci_irq_end(u_int irq);
+
+static struct hw_interrupt_type tsi108_pci_irq = {
+ "tsi108_PCI_int",
+ NULL,
+ NULL,
+ tsi108_pci_irq_enable,
+ tsi108_pci_irq_disable,
+ tsi108_pci_irq_ack,
+ tsi108_pci_irq_end,
+ NULL
+};
+
+#ifdef DBG_TSI108_INTERRUPT
+#define ASSERT(expr) if (!(expr)) { \
+ printk("tsi108pic :" \
+ "assertion failed! %s[%d]: %s\n", \
+ __FUNCTION__, __LINE__, #expr); \
+ dump_stack(); \
+ }
+#else
+#define ASSERT(expr) do {} while (0)
+#endif
+
+static inline u_int get_vector_offset(u_int irq)
+{
+ u_int offset;
+
+ if (irq < TSI108_IRQ_BASE || irq >= IRQ_PCI_INTAD_BASE)
+ return 0;
+
+ if (irq < IRQ_TSI108_MBOX0)
+ offset = TSI108_INT_IVPR(irq - TSI108_IRQ_BASE);
+ else if (irq < IRQ_TSI108_DBELL0)
+ offset = TSI108_INT_MBVPR(irq - IRQ_TSI108_MBOX0);
+ else if (irq < IRQ_TSI108_TIMER0)
+ offset = TSI108_INT_DVPR(irq - IRQ_TSI108_DBELL0);
+ else
+ offset = TSI108_INT_GTVPR(irq - IRQ_TSI108_TIMER0);
+
+ return offset;
+}
+
+static inline u_int tsi108_pic_read_reg(u_int reg_offset)
+{
+ return in_be32((volatile u32 *)(tsi108_pic_vir_addr + reg_offset));
+}
+
+static inline void tsi108_pic_write_reg(u_int reg_offset, u_int val)
+{
+ out_be32((volatile u32 *)(tsi108_pic_vir_addr + reg_offset), val);
+}
+
+void tsi108_pic_reset(void)
+{
+ tsi108_pic_write_reg(TSI108_INT_GCR, TSI108PIC_INT_GCR_R);
+ while (tsi108_pic_read_reg(TSI108_INT_GCR) & TSI108PIC_INT_GCR_R)
+ mb();
+}
+
+void tsi108_pic_set_output(int dest_num, u32 sense, u32 polarity)
+{
+ u32 temp = 0;
+ temp |= (IRQ_SENSE_LEVEL == sense) ?
+ (TSI108PIC_INT_CSR_S_LEVEL) : (TSI108PIC_INT_CSR_S_EDGE);
+ temp |= (IRQ_POLARITY_POSITIVE == polarity) ?
+ (TSI108PIC_INT_CSR_P_HIGH) : (TSI108PIC_INT_CSR_P_LOW);
+ tsi108_pic_write_reg(TSI108_INT_CSR(dest_num), temp);
+ mb();
+}
+
+int tsi108_pic_source_cfg(int src_num, /* interrupt source number */
+ u32 sense, /* interrupt source Sense */
+ u32 polarity, /* interrupt source Polarity */
+ TSI108_IRQ_MODE mode /* interrupt delivery Mode */
+ )
+{
+ unsigned temp;
+
+ temp = tsi108_pic_read_reg(TSI108_INT_IVPR(src_num));
+
+ if (temp & TSI108PIC_ACTIVITY) /* error if source is active */
+ return -1;
+
+ if (0 == (temp & TSI108PIC_MASK)) {
+ temp |= TSI108PIC_MASK; /* mask IRQ prior making changes */
+ tsi108_pic_write_reg(TSI108_INT_IVPR(src_num), temp);
+ }
+
+ temp &= ~(TSI108PIC_INT_IVPR_MODE |
+ TSI108PIC_INT_IVPR_S | TSI108PIC_INT_IVPR_P);
+
+ temp |= (IRQ_SENSE_LEVEL == sense) ?
+ (TSI108PIC_INT_CSR_S_LEVEL) : (TSI108PIC_INT_CSR_S_EDGE);
+ temp |= (IRQ_POLARITY_POSITIVE == polarity) ?
+ (TSI108PIC_INT_CSR_P_HIGH) : (TSI108PIC_INT_CSR_P_LOW);
+
+ tsi108_pic_write_reg(TSI108_INT_IVPR(src_num),
+ TSI108PIC_MASK | (mode << 29) | temp);
+ return (0);
+}
+
+int tsi108_pic_set_vector(int src_num, /* source number */
+ int vect, /* vector number */
+ int prio /* interrupt source priority */
+ )
+{
+ unsigned tmp;
+
+ tmp = tsi108_pic_read_reg(TSI108_INT_IVPR(src_num));
+
+ if (tmp & TSI108PIC_ACTIVITY) /* error if source is active */
+ return -1;
+
+ if (0 == (tmp & TSI108PIC_MASK)) {
+ tmp |= TSI108PIC_MASK; /* mask IRQ prior making changes */
+ tsi108_pic_write_reg(TSI108_INT_IVPR(src_num), tmp);
+ }
+
+ /* clear bits to be changed */
+ tmp &= ~(TSI108PIC_VECTOR_MASK | TSI108PIC_PRIORITY_MASK);
+
+ tmp |= (prio << 16) | vect;
+ tsi108_pic_write_reg(TSI108_INT_IVPR(src_num), tmp);
+ return 0;
+}
+
+void tsi108_pic_mask_all()
+{
+ int i;
+ unsigned int vp;
+
+ /* Mask all external and internal interrupt sources */
+ for (i = 0; i < TSI108PIC_MAX_SOURCES; i++) {
+ vp = tsi108_pic_read_reg(TSI108_INT_IVPR(i));
+ tsi108_pic_write_reg(TSI108_INT_IVPR(i), vp | TSI108PIC_MASK);
+ mb();
+
+ /* Make sure that irq is masked */
+ do {
+ vp = tsi108_pic_read_reg(TSI108_INT_IVPR(i));
+ } while ((vp & TSI108PIC_ACTIVITY) && !(vp & TSI108PIC_MASK));
+ }
+
+ /* Mask all timer interrupts */
+ for (i = 0; i < TSI108PIC_NUM_TIMERS; i++) {
+ vp = tsi108_pic_read_reg(TSI108_INT_GTVPR(i));
+ tsi108_pic_write_reg(TSI108_INT_GTVPR(i), vp | TSI108PIC_MASK);
+ mb();
+
+ do {
+ vp = tsi108_pic_read_reg(TSI108_INT_GTVPR(i));
+ } while ((vp & TSI108PIC_ACTIVITY) && !(vp & TSI108PIC_MASK));
+ }
+
+ /* Mask all doorbell interrupts */
+ for (i = 0; i < TSI108PIC_NUM_DBELLS; i++) {
+ vp = tsi108_pic_read_reg(TSI108_INT_DVPR(i));
+ tsi108_pic_write_reg(TSI108_INT_IVPR(i), vp | TSI108PIC_MASK);
+ mb();
+
+ do {
+ vp = tsi108_pic_read_reg(TSI108_INT_DVPR(i));
+ } while ((vp & TSI108PIC_ACTIVITY) && !(vp & TSI108PIC_MASK));
+ }
+
+ /* Mask all mailbox interrupts */
+ for (i = 0; i < 4; i++) {
+ vp = tsi108_pic_read_reg(TSI108_INT_MBVPR(i));
+ tsi108_pic_write_reg(TSI108_INT_MBVPR(i), vp | TSI108PIC_MASK);
+ mb();
+
+ do {
+ vp = tsi108_pic_read_reg(TSI108_INT_MBVPR(i));
+ } while ((vp & TSI108PIC_ACTIVITY) && !(vp & TSI108PIC_MASK));
+ }
+}
+
+/*
+ * The Tsi108 PC initialization routine.
+ * A caller routine (usually from platform-specific code has to provide
+ * sense/polarity configuration information for four external interrupt
+ * sources INT0 - INT3. This should be done in form of four-byte array
+ * (one byte per source ) that contains combination of sensitivity/polarity
+ * flags defined in asm-ppc/irq.h.
+ *
+ * Example of PIC initialization call is shown below:
+ *
+ * u_char your_board_pic_initsenses[] __initdata = {
+ * (IRQ_SENSE_LEVEL | IRQ_POLARITY_POSITIVE), // INT[0]
+ * (IRQ_SENSE_LEVEL | IRQ_POLARITY_POSITIVE), // INT[1]
+ * (IRQ_SENSE_LEVEL | IRQ_POLARITY_POSITIVE), // INT[2]
+ * (IRQ_SENSE_LEVEL | IRQ_POLARITY_POSITIVE) // INT[3]
+ * };
+ *
+ * tsi108_pic_init(your_board_pic_initsenses);
+ */
+
+void __init tsi108_pic_init(u_char * board_init_senses)
+{
+ u_int i;
+ u32 sense;
+
+ struct device_node *tsi_pic;
+ tsi_pic = of_find_node_by_type(NULL, "open-pic");
+ if (tsi_pic) {
+ unsigned int size;
+ void *prop = get_property(tsi_pic, "reg", &size);
+ tsi108_pic_phy_addr = of_translate_address(tsi_pic, prop);
+ }
+
+ DBG("%s: Tsi108 pic phy addr = 0x%x\n", __FUNCTION__,
+ (u32) tsi108_pic_phy_addr);
+ if (tsi108_pic_phy_addr == 0) {
+ printk("No tsi108 PIC found !\n");
+ return;
+ }
+
+ tsi108_pic_vir_addr = (u32) ioremap(tsi108_pic_phy_addr, 0x400);
+
+ tsi108_pic_reset();
+
+ if (ppc_md.progress)
+ ppc_md.progress("tsi108_pic_init: enter", 0x122);
+
+ /* Initialize timer interrupts */
+ for (i = 0; i < TSI108PIC_NUM_TIMERS; i++) {
+ /* Disabled, Priority 0 */
+ tsi108_pic_inittimer(i, 0, IRQ_TSI108_TIMER0 + i);
+ /* No processor */
+ tsi108_pic_maptimer(i, 0);
+ }
+
+ /* Init board-specific external sources */
+ for (i = 0; i < 4; i++) {
+ sense = board_init_senses[i];
+
+ if (sense & IRQ_SENSE_MASK)
+ irq_desc[TSI108_IRQ(i)].status |= IRQ_LEVEL;
+
+ /* Enabled, Priority 8 */
+ tsi108_pic_initirq(i, 8, TSI108_IRQ(i),
+ (sense & IRQ_POLARITY_MASK),
+ (sense & IRQ_SENSE_MASK));
+ /* Map to CPU #0 */
+ tsi108_pic_mapirq(TSI108_IRQ(i), 1 << 0, 0);
+ }
+
+ /* Init remaining internal sources. */
+ for (; i < TSI108PIC_MAX_SOURCES; i++) {
+ /* Disabled, Priority 8, by default - Positive Edge */
+ tsi108_pic_initirq(i, 8, TSI108_IRQ(i),
+ IRQ_POLARITY_POSITIVE, IRQ_SENSE_EDGE);
+ /* Map to CPU #0 */
+ tsi108_pic_mapirq(TSI108_IRQ(i), (1 << 0), 0);
+ }
+
+ /*
+ * Change sensitivity to level for sources that require it.
+ */
+
+ irq_desc[IRQ_TSI108_GIGE0].status |= IRQ_LEVEL;
+ irq_desc[IRQ_TSI108_GIGE1].status |= IRQ_LEVEL;
+ irq_desc[IRQ_TSI108_PCI].status |= IRQ_LEVEL;
+
+ /* Init descriptors */
+ for (i = 0; i < TSI108PIC_MAX_SOURCES; i++)
+ irq_desc[i + TSI108_IRQ_BASE].handler = &tsi108_pic_irq;
+
+ for (i = 0; i < NUM_PCI_IRQS; i++) {
+ irq_desc[i + IRQ_PCI_INTAD_BASE].handler = &tsi108_pci_irq;
+ irq_desc[i + IRQ_PCI_INTAD_BASE].status |= IRQ_LEVEL;
+ }
+
+ /* Initialize the spurious interrupt */
+ tsi108_pic_set_spurious(TSI108_IRQ_SPURIOUS);
+ tsi108_pic_set_task_priority(0);
+
+ init_pci_source();
+ tsi108_pic_enable_irq(IRQ_TSI108_PCI);
+
+ i = tsi108_pic_read_reg(TSI108_INT_VECTOR(0));
+ tsi108_pic_write_reg(TSI108_INT_EOI(0), 0);
+
+ if (ppc_md.progress)
+ ppc_md.progress("tsi108_pic_init: exit", 0x222);
+}
+
+/*
+ * Find out the current interrupt
+ */
+static u_int tsi108_pic_get_vect(void)
+{
+ u_int vec;
+
+ vec = tsi108_pic_read_reg(TSI108_INT_VECTOR(0)) & TSI108PIC_VECTOR_MASK;
+
+#ifdef DBG_TSI108_INTERRUPT
+ if (vec == TSI108_IRQ_SPURIOUS)
+ printk("TSI108: SPURIOUS vec=0x%08x\n", vec);
+ else
+ printk("TSI108: read vec=0x%08x\n", vec);
+#endif
+ return (vec);
+}
+
+static inline void tsi108_pic_eoi(void)
+{
+ tsi108_pic_write_reg(TSI108_INT_EOI(0), 0);
+ mb();
+}
+
+static void __init tsi108_pic_set_task_priority(u_int pri)
+{
+ ASSERT(pri >= 0 && pri < TSI108PIC_NUM_PRI);
+
+ tsi108_pic_write_reg(TSI108_INT_TASKP(0),
+ pri & TSI108PIC_INT_TASKP_TASKP);
+ mb();
+}
+
+static void tsi108_pic_set_spurious(u_int vec)
+{
+ ASSERT(vec == TSI108_IRQ_SPURIOUS);
+ tsi108_pic_write_reg(TSI108_INT_SVR, vec);
+ mb();
+}
+
+#ifdef CONFIG_SMP
+/*
+ * Convert a cpu mask from logical to physical cpu numbers.
+ */
+static inline u32 physmask(u32 cpumask)
+{
+ int i;
+ u32 mask = 0;
+
+ for (i = 0; i < NR_CPUS; ++i, cpumask >>= 1)
+ if (cpu_online(i))
+ mask |= (cpumask & 1) << smp_hw_index[i];
+ return mask;
+}
+#else
+#define physmask(cpumask) (cpumask)
+#endif
+
+/*
+ * Initialize a timer interrupt (and disable it)
+ *
+ * timer: timer number
+ * pri: interrupt source priority
+ * vec: the vector it will produce
+ */
+static void __init tsi108_pic_inittimer(u_int timer, u_int pri, u_int vec)
+{
+ unsigned int gtvpr;
+
+ ASSERT(timer >= 0 && timer < TSI108PIC_NUM_TIMERS);
+ ASSERT(pri >= 0 && pri < TSI108PIC_NUM_PRI);
+ ASSERT(vec >= 0 && vec < TSI108PIC_NUM_VECTORS);
+
+ gtvpr = tsi108_pic_read_reg(TSI108_INT_GTVPR(timer));
+ gtvpr &= ~(TSI108PIC_PRIORITY_MASK | TSI108PIC_VECTOR_MASK);
+ gtvpr |= (pri << 16) | vec;
+ tsi108_pic_write_reg(TSI108_INT_GTVPR(timer), gtvpr | TSI108PIC_MASK);
+ mb();
+}
+
+/*
+ * Map a timer interrupt to one or more CPUs
+ */
+static void __init tsi108_pic_maptimer(u_int timer, u_int cpumask)
+{
+ ASSERT(timer >= 0 && timer < TSI108PIC_NUM_TIMERS);
+
+ tsi108_pic_write_reg(TSI108_INT_GTDR(timer), physmask(cpumask));
+ mb();
+}
+
+/*
+ * Initalize the interrupt source which will generate an NMI.
+ * This raises the interrupt's priority from 8 to 9.
+ *
+ * irq: The logical IRQ which generates an NMI.
+ */
+void __init tsi108_pic_init_nmi_irq(u_int irq)
+{
+ u_int offset = get_vector_offset(irq);
+ u_int vpr = tsi108_pic_read_reg(offset);
+ vpr &= ~TSI108PIC_PRIORITY_MASK;
+ tsi108_pic_write_reg(offset, vpr | (9 << 16));
+ mb();
+}
+
+/*
+ *
+ * All functions below take an offset'ed irq argument
+ *
+ */
+
+/*
+ * Hookup a cascade to the tsi108 PIC.
+ */
+void __init
+tsi108_pic_hookup_cascade(u_int irq, char *name,
+ int (*cascade_fn) (struct pt_regs *))
+{
+ tsi108_pic_cascade_irq = irq;
+ tsi108_pic_cascade_fn = cascade_fn;
+ if (request_irq(irq, no_action, SA_INTERRUPT, name, NULL))
+ printk("Unable to get Tsi108 PIC IRQ %d for cascade\n",
+ irq - TSI108_IRQ_BASE);
+}
+
+/*
+ * Enable/disable an external interrupt source
+ *
+ * Externally called, irq is an offseted system-wide interrupt number
+ */
+static void tsi108_pic_enable_irq(u_int irq)
+{
+ u32 offset = get_vector_offset(irq);
+ u32 vpr = tsi108_pic_read_reg(offset);
+
+ /*
+ * Undo sensitivity change (see tsi108_pic_disable_irq())
+ */
+ if (irq_desc[irq].status & IRQ_LEVEL)
+ vpr |= TSI108PIC_INT_IVPR_S;
+
+ tsi108_pic_write_reg(offset, vpr & ~TSI108PIC_MASK);
+ mb();
+}
+
+static void tsi108_pic_disable_irq(u_int irq)
+{
+ u32 offset = get_vector_offset(irq);
+ u32 vpr = tsi108_pic_read_reg(offset);
+
+ /*
+ * Switch level interrupt to edge sensitivity to avoid generation
+ * of spurious interrupt request. See design note in Tsi108 PIC
+ * section of Tsi108 manual.
+ */
+ if (irq_desc[irq].status & IRQ_LEVEL)
+ vpr &= ~TSI108PIC_INT_IVPR_S;
+
+ tsi108_pic_write_reg(offset, vpr | TSI108PIC_MASK);
+ mb();
+ vpr = tsi108_pic_read_reg(offset);
+ if (!(vpr & TSI108PIC_MASK))
+ printk("TSI108_PIC: Error - Unable disable IRQ %d\n", irq);
+}
+
+/*
+ * Initialize an interrupt source (and disable it!)
+ *
+ * irq: Tsi108 PIC interrupt source number
+ * pri: interrupt source priority
+ * vec: the vector it will produce
+ * pol: polarity (1 for positive, 0 for negative)
+ * sense: 1 for level, 0 for edge
+ */
+static void __init
+tsi108_pic_initirq(u_int irq, u_int pri, u_int vec, int pol, int sense)
+{
+ unsigned int ivpr;
+
+ ivpr = TSI108PIC_MASK | (pri << 16) | vec;
+ ivpr |= (IRQ_SENSE_LEVEL == sense) ?
+ TSI108PIC_INT_IVPR_S_LEVEL : TSI108PIC_INT_IVPR_S_EDGE;
+ ivpr |= (IRQ_POLARITY_POSITIVE == pol) ?
+ TSI108PIC_INT_IVPR_P_HIGH : TSI108PIC_INT_IVPR_P_LOW;
+ tsi108_pic_write_reg(TSI108_INT_IVPR(irq), ivpr);
+ mb();
+}
+
+int tsi108_pic_set_irq_sense(int irq, /* PIC source number */
+ int pol, /* interrupt source polarity */
+ int sense /* interrupt source sense */
+ )
+{
+ unsigned int ivpr;
+
+ ivpr = tsi108_pic_read_reg(TSI108_INT_IVPR(irq));
+
+ if (ivpr & TSI108PIC_ACTIVITY) /* error if source is active */
+ return -1;
+
+ if (0 == (ivpr & TSI108PIC_MASK)) {
+ ivpr |= TSI108PIC_MASK; /* mask IRQ prior making changes */
+ tsi108_pic_write_reg(TSI108_INT_IVPR(irq), ivpr);
+ }
+
+ /* clear bits to be changed */
+ ivpr &= ~(TSI108PIC_INT_IVPR_P | TSI108PIC_INT_IVPR_S);
+
+ ivpr |= (IRQ_SENSE_LEVEL == sense) ?
+ TSI108PIC_INT_IVPR_S_LEVEL : TSI108PIC_INT_IVPR_S_EDGE;
+ ivpr |= (IRQ_POLARITY_POSITIVE == pol) ?
+ TSI108PIC_INT_IVPR_P_HIGH : TSI108PIC_INT_IVPR_P_LOW;
+
+ tsi108_pic_write_reg(TSI108_INT_IVPR(irq), ivpr);
+ return 0;
+}
+
+/*
+ * Map an interrupt source to one or more CPUs
+ */
+static void tsi108_pic_mapirq(u_int irq, u_int physmask, u_int keepmask)
+{
+ u_int offset = get_vector_offset(irq);
+
+ if (0 == offset)
+ return;
+ if (keepmask != 0)
+ physmask |= tsi108_pic_read_reg(offset + 4);
+ tsi108_pic_write_reg(offset + 4, physmask);
+ mb();
+}
+
+/* No spinlocks, should not be necessary with the Tsi108 PIC
+ * (1 register = 1 interrupt and we have the desc lock).
+ */
+static void tsi108_pic_ack_irq(unsigned int irq_nr)
+{
+ if ((irq_desc[irq_nr].status & IRQ_LEVEL) == 0)
+ tsi108_pic_eoi();
+}
+
+static void tsi108_pic_end_irq(unsigned int irq_nr)
+{
+ if ((irq_desc[irq_nr].status & IRQ_LEVEL) != 0)
+ tsi108_pic_eoi();
+}
+
+void tsi108_pic_set_affinity(unsigned int irq_nr, unsigned long cpumask)
+{
+ tsi108_pic_mapirq(irq_nr, physmask(cpumask), 0);
+}
+
+int tsi108_pic_get_irq(struct pt_regs *regs)
+{
+ int vector = tsi108_pic_get_vect();
+
+ if (vector == TSI108_IRQ_SPURIOUS) {
+ vector = -1;
+ }
+
+ if (vector == IRQ_TSI108_PCI) {
+ vector = get_pci_source(vector);
+ }
+
+ if (vector == -1) {
+ tsi108_pic_write_reg(TSI108_INT_EOI(0), 0);
+ }
+
+ return vector;
+}
+
+static void tsi108_pci_int_mask(u_int irq)
+{
+ u_int irp_cfg;
+ int int_line = (irq - IRQ_PCI_INTAD_BASE);
+
+ irp_cfg = tsi108_read_reg(TSI108_PCI_OFFSET + TSI108_PCI_IRP_CFG_CTL);
+ mb();
+ irp_cfg |= (1 << int_line); /* INTx_DIR = output */
+ irp_cfg &= ~(3 << (8 + (int_line * 2))); /* INTx_TYPE = unused */
+ tsi108_write_reg(TSI108_PCI_OFFSET + TSI108_PCI_IRP_CFG_CTL, irp_cfg);
+ mb();
+ irp_cfg = tsi108_read_reg(TSI108_PCI_OFFSET + TSI108_PCI_IRP_CFG_CTL);
+}
+
+static void tsi108_pci_int_unmask(u_int irq)
+{
+ u_int irp_cfg;
+ int int_line = (irq - IRQ_PCI_INTAD_BASE);
+
+ irp_cfg = tsi108_read_reg(TSI108_PCI_OFFSET + TSI108_PCI_IRP_CFG_CTL);
+ mb();
+ irp_cfg &= ~(1 << int_line);
+ irp_cfg |= (3 << (8 + (int_line * 2)));
+ tsi108_write_reg(TSI108_PCI_OFFSET + TSI108_PCI_IRP_CFG_CTL, irp_cfg);
+ mb();
+}
+
+static void tsi108_pci_irq_enable(u_int irq)
+{
+ tsi108_pci_int_unmask(irq);
+}
+
+static void tsi108_pci_irq_disable(u_int irq)
+{
+ tsi108_pci_int_mask(irq);
+}
+
+static void tsi108_pci_irq_ack(u_int irq)
+{
+ tsi108_pci_int_mask(irq);
+}
+
+static void tsi108_pci_irq_end(u_int irq)
+{
+ tsi108_pic_eoi(); /* eoi IRQ_TSI108_PCI */
+ tsi108_pci_int_unmask(irq);
+}
+
+static inline int get_pci_source(int vector)
+{
+ u_int temp = 0;
+ int irq = -1;
+ int i;
+ u_int pci_irp_stat;
+ static int mask = 0;
+
+ /* Read PCI/X block interrupt status register */
+ pci_irp_stat = tsi108_read_reg(TSI108_PCI_OFFSET + TSI108_PCI_IRP_STAT);
+ mb();
+
+ if (pci_irp_stat & TSI108_PCI_IRP_STAT_P_INT) {
+ /* Process Interrupt from PCI bus INTA# - INTD# lines */
+ temp =
+ tsi108_read_reg(TSI108_PCI_OFFSET +
+ TSI108_PCI_IRP_INTAD) & 0xf;
+ mb();
+ for (i = 0; i < 4; i++, mask++) {
+ if (temp & (1 << mask % 4)) {
+ irq = IRQ_PCI_INTA + mask % 4;
+ mask++;
+ break;
+ }
+ }
+ }
+#ifdef DBG_TSI108_INTERRUPT
+ else {
+ printk("TSI108_PIC: error in TSI108_PCI_IRP_STAT\n");
+ pci_irp_stat =
+ tsi108_read_reg(TSI108_PCI_OFFSET + TSI108_PCI_IRP_STAT);
+ temp =
+ tsi108_read_reg(TSI108_PCI_OFFSET + TSI108_PCI_IRP_INTAD);
+ mb();
+ printk(">> stat=0x%08x intad=0x%08x ", pci_irp_stat, temp);
+ temp =
+ tsi108_read_reg(TSI108_PCI_OFFSET + TSI108_PCI_IRP_CFG_CTL);
+ mb();
+ printk("cfg_ctl=0x%08x ", temp);
+ temp =
+ tsi108_read_reg(TSI108_PCI_OFFSET + TSI108_PCI_IRP_ENABLE);
+ mb();
+ printk("irp_enable=0x%08x\n", temp);
+ }
+#endif /* DBG_TSI108_INTERRUPT */
+
+ return irq;
+}
+
+static void init_pci_source(void)
+{
+ tsi108_write_reg(TSI108_PCI_OFFSET + TSI108_PCI_IRP_CFG_CTL,
+ 0x0000ff00);
+ tsi108_write_reg(TSI108_PCI_OFFSET + TSI108_PCI_IRP_ENABLE,
+ 0x00400000);
+ mb();
+}
+
+static struct sysdev_class tsi108_pic_sysclass = {
+ set_kset_name("tsi108_pic"),
+};
+
+static struct sys_device device_tsi108_pic = {
+ .id = 0,
+ .cls = &tsi108_pic_sysclass,
+};
+
+static struct sysdev_driver driver_tsi108_pic = {
+#ifdef CONFIG_PM /* FIXME: placeholder for future development */
+ .suspend = &tsi108_pic_suspend,
+ .resume = &tsi108_pic_resume,
+#endif /* CONFIG_PM */
+};
+
+static int __init init_tsi108_pic_sysfs(void)
+{
+ int rc;
+
+ if (!get_csrbase())
+ return -ENODEV;
+ printk(KERN_DEBUG "Registering tsi108_pic with sysfs...\n");
+ rc = sysdev_class_register(&tsi108_pic_sysclass);
+ if (rc) {
+ printk(KERN_ERR "Failed registering tsi108_pic sys class\n");
+ return -ENODEV;
+ }
+ rc = sysdev_register(&device_tsi108_pic);
+ if (rc) {
+ printk(KERN_ERR "Failed registering tsi108_pic sys device\n");
+ return -ENODEV;
+ }
+ rc = sysdev_driver_register(&tsi108_pic_sysclass, &driver_tsi108_pic);
+ if (rc) {
+ printk(KERN_ERR "Failed registering tsi108_pic sys driver\n");
+ return -ENODEV;
+ }
+ return 0;
+}
+
+subsys_initcall(init_tsi108_pic_sysfs);
diff --git a/include/asm-powerpc/tsi108_pic.h b/include/asm-powerpc/tsi108_pic.h
new file mode 100644
index 0000000..7b23352
--- /dev/null
+++ b/include/asm-powerpc/tsi108_pic.h
@@ -0,0 +1,232 @@
+/*
+ * (C) Copyright 2005 Tundra Semiconductor Corp.
+ * Alex Bounine, <alexandreb@tundra.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
+ */
+
+/*
+ * arch/ppc/syslib/tsi108_pic.h - Tsi108 Interrupt Controller definitions
+ */
+
+#ifndef _LINUX_TSI108_PIC_H
+#define _LINUX_TSI108_PIC_H
+
+#include <asm/tsi108_irq.h>
+
+#ifdef __KERNEL__
+
+/*
+ * Tsi108 PIC supports up to 24 interrupt sources and up to 4 processors
+ */
+
+#define TSI108PIC_MAX_SOURCES 24
+#define TSI108PIC_MAX_PROCESSORS 4
+
+#define TSI108PIC_NUM_TIMERS 4
+#define TSI108PIC_NUM_DBELLS 4
+#define TSI108PIC_NUM_PROC 4
+#define TSI108PIC_NUM_PRI 16
+#define TSI108PIC_NUM_VECTORS 256
+
+/*
+ * Tsi108 PIC Register offsets within block.
+ */
+
+/* Registers controlling sources */
+#define TSI108_INT_FRR (0x000)
+#define TSI108_INT_GCR (0x004)
+#define TSI108_INT_SVR (0x010)
+#define TSI108_INT_GTVPR(n) (0x38 + 0x10*(n))
+#define TSI108_INT_GTDR(n) (0x3C + 0x10*(n))
+#define TSI108_INT_IVPR(n) (0x100 + 0x8*(n))
+#define TSI108_INT_IDR(n) (0x104 + 0x8*(n))
+#define TSI108_INT_DVPR(n) (0x204 + 0xC*(n))
+#define TSI108_INT_DDR(n) (0x208 + 0xC*(n))
+#define TSI108_INT_MBVPR(n) (0x284 + 0x10*(n))
+#define TSI108_INT_MBDR(n) (0x288 + 0x10*(n))
+
+/* Registers controlling destinations */
+#define TSI108_INT_TASKP(n) (0x300 + 0x40*(n))
+#define TSI108_INT_VECTOR(n) (0x304 + 0x40*(n))
+#define TSI108_INT_EOI(n) (0x308 + 0x40*(n))
+#define TSI108_INT_CSR(n) (0x30C + 0x40*(n))
+
+/*
+ * Generic definitions common for different types of interrupt
+ * sources.
+ */
+
+#define TSI108PIC_MASK (0x80000000)
+#define TSI108PIC_ACTIVITY (0x40000000)
+#define TSI108PIC_PRIORITY_MASK (0x000f0000)
+#define TSI108PIC_VECTOR_MASK (0x000000ff)
+
+/**********************************************************
+ * Register Bit Masks definitions for every register
+ */
+
+/* TSI108PIC_INT_FRR : Register Bits Masks Definitions */
+#define TSI108PIC_INT_FRR_VID (0x000000ff)
+#define TSI108PIC_INT_FRR_NCPU (0x00001f00)
+#define TSI108PIC_INT_FRR_NITM (0x0000e000)
+#define TSI108PIC_INT_FRR_NIRQ (0x07ff0000)
+#define TSI108PIC_INT_FRR_NIDOOR (0xe0000000)
+#define TSI108PIC_INT_FRR_RESERVED (0x18000000)
+
+/* TSI108PIC_INT_GCR : Register Bits Masks Definitions */
+#define TSI108PIC_INT_GCR_R (0x80000000)
+#define TSI108PIC_INT_GCR_RESERVED (0x7fffffff)
+
+/* TSI108PIC_INT_ICR : Register Bits Masks Definitions */
+#define TSI108PIC_INT_ICR_R (0x0000000f)
+#define TSI108PIC_INT_ICR_RESERVED (0xfffffff0)
+
+/* TSI108PIC_INT_MVI : Register Bits Masks Definitions */
+#define TSI108PIC_INT_MVI_VID (0x000000ff)
+#define TSI108PIC_INT_MVI_DID (0x0000ff00)
+#define TSI108PIC_INT_MVI_STEP (0x00ff0000)
+#define TSI108PIC_INT_MVI_RESERVED (0xff000000)
+
+/* TSI108PIC_INT_SVR : Register Bits Masks Definitions */
+#define TSI108PIC_INT_SVR_VECTOR (0x000000ff)
+#define TSI108PIC_INT_SVR_RESERVED (0xffffff00)
+
+/* TSI108PIC_INT_TFRR : Register Bits Masks Definitions */
+#define TSI108PIC_INT_TFRR_TIME_FREQ (0xffffffff)
+
+/* TSI108PIC_INT_SOFT_SET : Register Bits Masks Definitions */
+#define TSI108PIC_INT_SOFT_SET_S (0x00ffffff)
+#define TSI108PIC_INT_SOFT_SET_RESERVED (0xff000000)
+
+/* TSI108PIC_INT_SOFT_ENABLE : Register Bits Masks Definitions */
+#define TSI108PIC_INT_SOFT_ENABLE_EN (0x00ffffff)
+#define TSI108PIC_INT_SOFT_ENABLE_RESERVED (0xff000000)
+
+/* TSI108PIC_INT_GTCCR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_GTCCR_COUNT (0x7fffffff)
+#define TSI108PIC_INT_GTCCR_T (0x80000000)
+
+/* TSI108PIC_INT_GTBCR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_GTBCR_B_COUNT (0x7fffffff)
+#define TSI108PIC_INT_GTBCR_CI (0x80000000)
+
+/* TSI108PIC_INT_GTVPR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_GTVPR_VECTOR (0x000000ff)
+#define TSI108PIC_INT_GTVPR_PRIORITY (0x000f0000)
+#define TSI108PIC_INT_GTVPR_PRESCALE (0x00f00000)
+#define TSI108PIC_INT_GTVPR_A (0x40000000)
+#define TSI108PIC_INT_GTVPR_M (0x80000000)
+#define TSI108PIC_INT_GTVPR_RESERVED (0x3f00ff00)
+
+/* TSI108PIC_INT_GTDR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_GTDR_SEL_OUT (0x0000000f)
+#define TSI108PIC_INT_GTDR_RESERVED (0xfffffff0)
+
+/* TSI108PIC_INT_IVPR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_IVPR_VECTOR (0x000000ff)
+#define TSI108PIC_INT_IVPR_PRIORITY (0x000f0000)
+
+#define TSI108PIC_INT_IVPR_P (0x01000000)
+#define TSI108PIC_INT_IVPR_P_LOW (0 << 24)
+#define TSI108PIC_INT_IVPR_P_HIGH (1 << 24)
+
+#define TSI108PIC_INT_IVPR_S (0x02000000)
+#define TSI108PIC_INT_IVPR_S_EDGE (0 << 25)
+#define TSI108PIC_INT_IVPR_S_LEVEL (1 << 25)
+
+#define TSI108PIC_INT_IVPR_MODE (0x20000000)
+#define TSI108PIC_INT_IVPR_A (0x40000000)
+#define TSI108PIC_INT_IVPR_M (0x80000000)
+#define TSI108PIC_INT_IVPR_RESERVED (0x1cf0ff00)
+
+/* TSI108PIC_INT_IDR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_IDR_SEL_OUT (0x0000000f)
+#define TSI108PIC_INT_IDR_RESERVED (0xfffffff0)
+
+/* TSI108PIC_INT_DAR : Register Bits Masks Definitions */
+#define TSI108PIC_INT_DAR_A (0x0000000f)
+#define TSI108PIC_INT_DAR_RESERVED (0xfffffff0)
+
+/* TSI108PIC_INT_DVPR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_DVPR_VECTOR (0x000000ff)
+#define TSI108PIC_INT_DVPR_PRIORITY (0x000f0000)
+#define TSI108PIC_INT_DVPR_A (0x40000000)
+#define TSI108PIC_INT_DVPR_M (0x80000000)
+#define TSI108PIC_INT_DVPR_RESERVED (0x3ff0ff00)
+
+/* TSI108PIC_INT_DDR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_DDR_SEL_OUT (0x0000000f)
+#define TSI108PIC_INT_DDR_RESERVED (0xfffffff0)
+
+/* TSI108PIC_INT_DMR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_DMR_M (0xffffffff)
+
+/* TSI108PIC_INT_MBR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_MBR_M (0xffffffff)
+
+/* TSI108PIC_INT_MBVPR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_MBVPR_VECTOR (0x000000ff)
+#define TSI108PIC_INT_MBVPR_PRIORITY (0x000f0000)
+#define TSI108PIC_INT_MBVPR_A (0x40000000)
+#define TSI108PIC_INT_MBVPR_M (0x80000000)
+#define TSI108PIC_INT_MBVPR_RESERVED (0x3ff0ff00)
+
+/* TSI108PIC_INT_MBDR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_MBDR_SEL_OUT (0x0000000f)
+#define TSI108PIC_INT_MBDR_RESERVED (0xfffffff0)
+
+/* TSI108PIC_INT_TASKP(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_TASKP_TASKP (0x0000000f)
+#define TSI108PIC_INT_TASKP_RESERVED (0xfffffff0)
+
+/* TSI108PIC_INT_VECTOR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_VECTOR_VECTOR (0x000000ff)
+#define TSI108PIC_INT_VECTOR_LS_VECTOR (0xff000000)
+#define TSI108PIC_INT_VECTOR_RESERVED (0x00ffff00)
+
+/* TSI108PIC_INT_EOI(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_EOI_EOI (0x000000ff)
+#define TSI108PIC_INT_EOI_RESERVED (0xffffff00)
+
+/* TSI108PIC_INT_CSR(X) : Register Bits Masks Definitions */
+#define TSI108PIC_INT_CSR_RESERVED (0xfffffffc)
+
+#define TSI108PIC_INT_CSR_P (1 << 0)
+#define TSI108PIC_INT_CSR_P_LOW (0 << 0)
+#define TSI108PIC_INT_CSR_P_HIGH (1 << 0)
+
+#define TSI108PIC_INT_CSR_S (1 << 1)
+#define TSI108PIC_INT_CSR_S_EDGE (0 << 1)
+#define TSI108PIC_INT_CSR_S_LEVEL (1 << 1)
+
+extern void tsi108_pic_init(u_char * board_init_senses);
+extern void tsi108_pic_reset(void);
+extern void tsi108_pic_set_output(int dest_num, u32 sense, u32 polarity);
+extern int tsi108_pic_source_cfg(int src_num, u32 sense,
+ u32 polarity, TSI108_IRQ_MODE mode);
+extern int tsi108_pic_set_vector(int src_num, int vect, int prio);
+extern void tsi108_pic_init_nmi_irq(u_int irq);
+extern void tsi108_pic_hookup_cascade(u_int irq, char *name,
+ int (*cascade_fn) (struct pt_regs *));
+extern int tsi108_pic_get_irq(struct pt_regs *regs);
+
+#endif /* __KERNEL__ */
+
+#endif /* _LINUX_TSI108_PIC_H */
--
1.3.0
^ permalink raw reply related
* [PATCH/2.6.17-rc4 5/10] Add tsi108 Ethernet support
From: Zang Roy-r61911 @ 2006-05-17 10:14 UTC (permalink / raw)
To: Paul Mackerras; +Cc: linuxppc-dev list, Alexandre.Bounine, Yang Xin-Xin-r48390
This patch adds a device driver and configuration options for
Tundra Semiconductor Tsi108 integrated dual port Gigabit Ethernet controller
Signed-off-by: Alexandre Bounine <alexandreb@tundra.com>
Signed-off-by: Roy Zang <tie-fei.zang@freescale.com>
---
drivers/net/Kconfig | 8
drivers/net/Makefile | 1
drivers/net/tsi108_eth.c | 1740 ++++++++++++++++++++++++++++++++++++++++++++++
drivers/net/tsi108_eth.h | 404 +++++++++++
4 files changed, 2153 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/tsi108_eth.c
create mode 100644 drivers/net/tsi108_eth.h
23ed1c55ab6bc7a4c1c76f646579a1bc195bc7d2
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index bdaaad8..8a4ef29 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -2177,6 +2177,14 @@ config SPIDER_NET
This driver supports the Gigabit Ethernet chips present on the
Cell Processor-Based Blades from IBM.
+config TSI108_ETH
+ tristate "Tundra TSI108 gigabit Ethernet support"
+ depends on TSI108_BRIDGE
+ help
+ This driver supports Tundra TSI108 gigabit Ethernet ports.
+ To compile this driver as a module, choose M here: the module
+ will be called tsi108_eth.
+
config GIANFAR
tristate "Gianfar Ethernet"
depends on 85xx || 83xx
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index b90468a..5f90b30 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -109,6 +109,7 @@ obj-$(CONFIG_B44) += b44.o
obj-$(CONFIG_FORCEDETH) += forcedeth.o
obj-$(CONFIG_NE_H8300) += ne-h8300.o 8390.o
+obj-$(CONFIG_TSI108_ETH) += tsi108_eth.o
obj-$(CONFIG_MV643XX_ETH) += mv643xx_eth.o
obj-$(CONFIG_PPP) += ppp_generic.o slhc.o
diff --git a/drivers/net/tsi108_eth.c b/drivers/net/tsi108_eth.c
new file mode 100644
index 0000000..cb67dbe
--- /dev/null
+++ b/drivers/net/tsi108_eth.c
@@ -0,0 +1,1740 @@
+/*******************************************************************************
+
+ Copyright(c) 2005 Tundra Semiconductor Corporation.
+
+ 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.
+
+*******************************************************************************/
+
+/* This driver is based on the driver code originally developed
+ * for the Intel IOC80314 (ForestLake) Gigabit Ethernet by
+ * scott.wood@timesys.com * Copyright (C) 2003 TimeSys Corporation
+ *
+ * Currently changes from original version are:
+ * - portig to Tsi108-based platform and kernel 2.6 (kong.lai@tundra.com)
+ * - modifications to handle two ports independently and support for
+ * additional PHY devices (alexandre.bounine@tundra.com)
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/net.h>
+#include <linux/netdevice.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/crc32.h>
+#include <linux/mii.h>
+#include <linux/device.h>
+#include <asm/system.h>
+#include <asm/io.h>
+#include <linux/pci.h>
+#include <linux/rtnetlink.h>
+#include <linux/timer.h>
+
+#include <asm/tsi108_irq.h>
+#include <asm/tsi108.h>
+#include "tsi108_eth.h"
+
+typedef struct net_device net_device;
+typedef struct sk_buff sk_buff;
+
+#define MII_READ_DELAY 10000 /* max link wait time in msec */
+
+#define TSI108_RXRING_LEN 256
+
+/* NOTE: The driver currently does not support receiving packets
+ * larger than the buffer size, so don't decrease this (unless you
+ * want to add such support).
+ */
+#define TSI108_RXBUF_SIZE 1536
+
+#define TSI108_TXRING_LEN 256
+
+#define TSI108_TX_INT_FREQ 64
+
+/* Check the phy status every half a second. */
+#define CHECK_PHY_INTERVAL (HZ/2)
+
+extern hw_info hw_info_table[];
+
+typedef struct {
+ volatile u32 regs; /* Base of normal regs */
+ volatile u32 phyregs; /* Base of register bank used for PHY access */
+ int phy; /* Index of PHY for this interface */
+ int irq_num;
+
+ struct timer_list timer;/* Timer that triggers the check phy function */
+ int rxtail; /* Next entry in rxring to read */
+ int rxhead; /* Next entry in rxring to give a new buffer */
+ int rxfree; /* Number of free, allocated RX buffers */
+
+ int rxpending; /* Non-zero if there are still descriptors
+ * to be processed from a previous descriptor
+ * interrupt condition that has been cleared */
+
+ int txtail; /* Next TX descriptor to check status on */
+ int txhead; /* Next TX descriptor to use */
+
+ /* Number of free TX descriptors. This could be calculated from
+ * rxhead and rxtail if one descriptor were left unused to disambiguate
+ * full and empty conditions, but it's simpler to just keep track
+ * explicitly. */
+
+ int txfree;
+
+ int phy_ok; /* The PHY is currently powered on. */
+
+ /* PHY status (duplex is 1 for half, 2 for full,
+ * so that the default 0 indicates that neither has
+ * yet been configured). */
+
+ int link_up;
+ int speed;
+ int duplex;
+
+ tx_desc *txring;
+ rx_desc *rxring;
+ sk_buff *txskbs[TSI108_TXRING_LEN];
+ sk_buff *rxskbs[TSI108_RXRING_LEN];
+
+ dma_addr_t txdma, rxdma;
+
+ /* txlock nests in misclock and phy_lock */
+
+ spinlock_t txlock, misclock;
+
+ /* stats is used to hold the upper bits of each hardware counter,
+ * and tmpstats is used to hold the full values for returning
+ * to the caller of get_stats(). They must be separate in case
+ * an overflow interrupt occurs before the stats are consumed.
+ */
+
+ struct net_device_stats stats;
+ struct net_device_stats tmpstats;
+
+ /* These stats are kept separate in hardware, thus require individual
+ * fields for handling carry. They are combined in get_stats.
+ */
+
+ unsigned long rx_fcs; /* Add to rx_frame_errors */
+ unsigned long rx_short_fcs; /* Add to rx_frame_errors */
+ unsigned long rx_long_fcs; /* Add to rx_frame_errors */
+ unsigned long rx_underruns; /* Add to rx_length_errors */
+ unsigned long rx_overruns; /* Add to rx_length_errors */
+
+ unsigned long tx_coll_abort; /* Add to tx_aborted_errors/collisions */
+ unsigned long tx_pause_drop; /* Add to tx_aborted_errors */
+
+ unsigned long mc_hash[16];
+} tsi108_prv_data;
+static void tsi108_timed_checker(unsigned long dev_ptr);
+
+static net_device *tsi108_devs[TSI108_ETH_PORT_NUM];
+
+static void dump_eth_one(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+
+ printk("Dumping %s...\n", dev->name);
+ printk("intstat %x intmask %x phy_ok %d"
+ " link %d speed %d duplex %d\n",
+ TSI108_ETH_READ_REG(TSI108_EC_INTSTAT),
+ TSI108_ETH_READ_REG(TSI108_EC_INTMASK), data->phy_ok,
+ data->link_up, data->speed, data->duplex);
+
+ printk("TX: head %d, tail %d, free %d, stat %x, estat %x, err %x\n",
+ data->txhead, data->txtail, data->txfree,
+ TSI108_ETH_READ_REG(TSI108_EC_TXSTAT),
+ TSI108_ETH_READ_REG(TSI108_EC_TXESTAT),
+ TSI108_ETH_READ_REG(TSI108_EC_TXERR));
+
+ printk("RX: head %d, tail %d, free %d, stat %x,"
+ " estat %x, err %x, pending %d\n\n",
+ data->rxhead, data->rxtail, data->rxfree,
+ TSI108_ETH_READ_REG(TSI108_EC_RXSTAT),
+ TSI108_ETH_READ_REG(TSI108_EC_RXESTAT),
+ TSI108_ETH_READ_REG(TSI108_EC_RXERR), data->rxpending);
+}
+
+void tsi108_dump_eth(void)
+{
+ dump_eth_one(tsi108_devs[0]);
+ dump_eth_one(tsi108_devs[1]);
+}
+
+/* Synchronization is needed between the thread and up/down events.
+ * Note that the PHY is accessed through the same registers for both
+ * interfaces, so this can't be made interface-specific.
+ */
+
+static spinlock_t phy_lock = SPIN_LOCK_UNLOCKED;
+
+static inline u16 tsi108_read_mii(tsi108_prv_data * data, int reg, int *status)
+{
+ int i;
+ u16 ret;
+
+ TSI108_ETH_WRITE_PHYREG(TSI108_MAC_MII_ADDR,
+ (data->phy << TSI108_MAC_MII_ADDR_PHY) |
+ (reg << TSI108_MAC_MII_ADDR_REG));
+ mb();
+ TSI108_ETH_WRITE_PHYREG(TSI108_MAC_MII_CMD, 0);
+ mb();
+ TSI108_ETH_WRITE_PHYREG(TSI108_MAC_MII_CMD, TSI108_MAC_MII_CMD_READ);
+ mb();
+ for (i = 0; i < 100; i++) {
+ if (!(TSI108_ETH_READ_PHYREG(TSI108_MAC_MII_IND) &
+ (TSI108_MAC_MII_IND_NOTVALID | TSI108_MAC_MII_IND_BUSY)))
+ break;
+ udelay(10);
+ }
+
+ if (i == 100) {
+ if (status)
+ *status = -EBUSY;
+
+ ret = 0xffff;
+ } else {
+ if (status)
+ *status = 0;
+
+ ret = TSI108_ETH_READ_PHYREG(TSI108_MAC_MII_DATAIN);
+ }
+
+ return ret;
+}
+
+static inline void tsi108_write_mii(tsi108_prv_data * data, int reg, u16 val)
+{
+ TSI108_ETH_WRITE_PHYREG(TSI108_MAC_MII_ADDR,
+ (data->phy << TSI108_MAC_MII_ADDR_PHY) |
+ (reg << TSI108_MAC_MII_ADDR_REG));
+ mb();
+ TSI108_ETH_WRITE_PHYREG(TSI108_MAC_MII_DATAOUT, val);
+ mb();
+ while (TSI108_ETH_READ_PHYREG(TSI108_MAC_MII_IND) &
+ TSI108_MAC_MII_IND_BUSY) ;
+}
+
+static inline void tsi108_write_tbi(tsi108_prv_data * data, int reg, u16 val)
+{
+
+ TSI108_ETH_WRITE_REG(TSI108_MAC_MII_ADDR,
+ (0x1e << TSI108_MAC_MII_ADDR_PHY)
+ | (reg << TSI108_MAC_MII_ADDR_REG));
+ mb();
+
+ TSI108_ETH_WRITE_REG(TSI108_MAC_MII_DATAOUT, val);
+ mb();
+ while (TSI108_ETH_READ_REG(TSI108_MAC_MII_IND) &
+ TSI108_MAC_MII_IND_BUSY) ;
+}
+
+static void tsi108_check_phy(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ u16 sumstat;
+ u32 mac_cfg2_reg, portctrl_reg;
+ u32 fdx_flag = 0, reg_update = 0;
+
+ /* Do a dummy read, as for some reason the first read
+ * after a link becomes up returns link down, even if
+ * it's been a while since the link came up.
+ */
+
+ spin_lock(&phy_lock);
+
+ if (!data->phy_ok)
+ goto out;
+
+ tsi108_read_mii(data, PHY_STAT, NULL);
+
+ if (!(tsi108_read_mii(data, PHY_STAT, NULL) & PHY_STAT_LINKUP)) {
+ if (data->link_up == 1) {
+ netif_stop_queue(dev);
+ data->link_up = 0;
+ printk(KERN_NOTICE "%s : link is down\n", dev->name);
+ netif_carrier_off(dev);
+ }
+
+ goto out;
+ }
+
+ {
+ mac_cfg2_reg = TSI108_ETH_READ_REG(TSI108_MAC_CFG2);
+ portctrl_reg = TSI108_ETH_READ_REG(TSI108_EC_PORTCTRL);
+
+ sumstat = tsi108_read_mii(data, PHY_SUM_STAT, NULL);
+
+ switch (sumstat & PHY_SUM_STAT_SPEED_MASK) {
+ case PHY_SUM_STAT_1000T_FD:
+ fdx_flag++;
+ case PHY_SUM_STAT_1000T_HD:
+ if (data->speed != 1000) {
+ mac_cfg2_reg &= ~TSI108_MAC_CFG2_IFACE_MASK;
+ mac_cfg2_reg |= TSI108_MAC_CFG2_GIG;
+ portctrl_reg &= ~TSI108_EC_PORTCTRL_NOGIG;
+ data->speed = 1000;
+ reg_update++;
+ }
+ break;
+ case PHY_SUM_STAT_100TX_FD:
+ fdx_flag++;
+ case PHY_SUM_STAT_100TX_HD:
+ if (data->speed != 100) {
+ mac_cfg2_reg &= ~TSI108_MAC_CFG2_IFACE_MASK;
+ mac_cfg2_reg |= TSI108_MAC_CFG2_NOGIG;
+ portctrl_reg |= TSI108_EC_PORTCTRL_NOGIG;
+ data->speed = 100;
+ reg_update++;
+ }
+ break;
+
+ case PHY_SUM_STAT_10T_FD:
+ fdx_flag++;
+ case PHY_SUM_STAT_10T_HD:
+ if (data->speed != 10) {
+ mac_cfg2_reg &= ~TSI108_MAC_CFG2_IFACE_MASK;
+ mac_cfg2_reg |= TSI108_MAC_CFG2_NOGIG;
+ portctrl_reg |= TSI108_EC_PORTCTRL_NOGIG;
+ data->speed = 10;
+ reg_update++;
+ }
+ break;
+
+ default:
+ if (net_ratelimit())
+ printk(KERN_ERR "PHY reported invalid speed,"
+ KERN_ERR " summary status %x\n",
+ sumstat);
+ goto out;
+ }
+
+ if (fdx_flag || (sumstat & PHY_SUM_STAT_FULLDUPLEX)) {
+ if (data->duplex != 2) {
+ mac_cfg2_reg |= TSI108_MAC_CFG2_FULLDUPLEX;
+ portctrl_reg &= ~TSI108_EC_PORTCTRL_HALFDUPLEX;
+ data->duplex = 2;
+ reg_update++;
+ }
+ } else {
+ if (data->duplex != 1) {
+ mac_cfg2_reg &= ~TSI108_MAC_CFG2_FULLDUPLEX;
+ portctrl_reg |= TSI108_EC_PORTCTRL_HALFDUPLEX;
+ data->duplex = 1;
+ reg_update++;
+ }
+ }
+
+ if (reg_update) {
+ TSI108_ETH_WRITE_REG(TSI108_MAC_CFG2, mac_cfg2_reg);
+ mb();
+ TSI108_ETH_WRITE_REG(TSI108_EC_PORTCTRL, portctrl_reg);
+ mb();
+ }
+
+ }
+
+ if (data->link_up == 0) {
+ /* The manual says it can take 3-4 usecs for the speed change
+ * to take effect.
+ */
+ udelay(5);
+
+ spin_lock(&data->txlock);
+ if (netif_queue_stopped(dev)
+ && is_valid_ether_addr(dev->dev_addr) && data->txfree)
+ netif_wake_queue(dev);
+
+ data->link_up = 1;
+ spin_unlock(&data->txlock);
+ printk("%s : link is up: %dMb %s-duplex\n",
+ dev->name, data->speed,
+ (data->duplex == 2) ? "full" : "half");
+ netif_carrier_on(dev);
+ }
+
+ out:
+ spin_unlock(&phy_lock);
+}
+
+static inline void
+tsi108_stat_carry_one(int carry, int carry_bit, int carry_shift,
+ unsigned long *upper)
+{
+ if (carry & carry_bit)
+ *upper += carry_shift;
+}
+
+static void tsi108_stat_carry(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ u32 carry1, carry2;
+
+ spin_lock_irq(&data->misclock);
+
+ carry1 = TSI108_ETH_READ_REG(TSI108_STAT_CARRY1);
+ carry2 = TSI108_ETH_READ_REG(TSI108_STAT_CARRY2);
+
+ TSI108_ETH_WRITE_REG(TSI108_STAT_CARRY1, carry1);
+ TSI108_ETH_WRITE_REG(TSI108_STAT_CARRY2, carry2);
+
+ tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXBYTES,
+ TSI108_STAT_RXBYTES_CARRY, &data->stats.rx_bytes);
+
+ tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXPKTS,
+ TSI108_STAT_RXPKTS_CARRY,
+ &data->stats.rx_packets);
+
+ tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXFCS,
+ TSI108_STAT_RXFCS_CARRY, &data->rx_fcs);
+
+ tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXMCAST,
+ TSI108_STAT_RXMCAST_CARRY,
+ &data->stats.multicast);
+
+ tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXALIGN,
+ TSI108_STAT_RXALIGN_CARRY,
+ &data->stats.rx_frame_errors);
+
+ tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXLENGTH,
+ TSI108_STAT_RXLENGTH_CARRY,
+ &data->stats.rx_length_errors);
+
+ tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXRUNT,
+ TSI108_STAT_RXRUNT_CARRY, &data->rx_underruns);
+
+ tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXJUMBO,
+ TSI108_STAT_RXJUMBO_CARRY, &data->rx_overruns);
+
+ tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXFRAG,
+ TSI108_STAT_RXFRAG_CARRY, &data->rx_short_fcs);
+
+ tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXJABBER,
+ TSI108_STAT_RXJABBER_CARRY, &data->rx_long_fcs);
+
+ tsi108_stat_carry_one(carry1, TSI108_STAT_CARRY1_RXDROP,
+ TSI108_STAT_RXDROP_CARRY,
+ &data->stats.rx_missed_errors);
+
+ tsi108_stat_carry_one(carry2, TSI108_STAT_CARRY2_TXBYTES,
+ TSI108_STAT_TXBYTES_CARRY, &data->stats.tx_bytes);
+
+ tsi108_stat_carry_one(carry2, TSI108_STAT_CARRY2_TXPKTS,
+ TSI108_STAT_TXPKTS_CARRY,
+ &data->stats.tx_packets);
+
+ tsi108_stat_carry_one(carry2, TSI108_STAT_CARRY2_TXEXDEF,
+ TSI108_STAT_TXEXDEF_CARRY,
+ &data->stats.tx_aborted_errors);
+
+ tsi108_stat_carry_one(carry2, TSI108_STAT_CARRY2_TXEXCOL,
+ TSI108_STAT_TXEXCOL_CARRY, &data->tx_coll_abort);
+
+ tsi108_stat_carry_one(carry2, TSI108_STAT_CARRY2_TXTCOL,
+ TSI108_STAT_TXTCOL_CARRY,
+ &data->stats.collisions);
+
+ tsi108_stat_carry_one(carry2, TSI108_STAT_CARRY2_TXPAUSE,
+ TSI108_STAT_TXPAUSEDROP_CARRY,
+ &data->tx_pause_drop);
+
+ spin_unlock_irq(&data->misclock);
+}
+
+/* Read a stat counter atomically with respect to carries.
+ * data->misclock must be held.
+ */
+static inline unsigned long
+tsi108_read_stat(tsi108_prv_data * data, int reg, int carry_bit,
+ int carry_shift, unsigned long *upper)
+{
+ int carryreg;
+ unsigned long val;
+
+ if (reg < 0xb0)
+ carryreg = TSI108_STAT_CARRY1;
+ else
+ carryreg = TSI108_STAT_CARRY2;
+
+ again:
+ val = TSI108_ETH_READ_REG(reg) | *upper;
+
+ rmb();
+
+ /* Check to see if it overflowed, but the interrupt hasn't
+ * been serviced yet. If so, handle the carry here, and
+ * try again.
+ */
+
+ if (unlikely(TSI108_ETH_READ_REG(carryreg) & carry_bit)) {
+ *upper += carry_shift;
+ TSI108_ETH_WRITE_REG(carryreg, carry_bit);
+ mb();
+
+ goto again;
+ }
+
+ return val;
+}
+
+static struct net_device_stats *tsi108_get_stats(net_device * dev)
+{
+ unsigned long excol;
+
+ tsi108_prv_data *data = netdev_priv(dev);
+ spin_lock_irq(&data->misclock);
+
+ data->tmpstats.rx_packets =
+ tsi108_read_stat(data, TSI108_STAT_RXPKTS,
+ TSI108_STAT_CARRY1_RXPKTS,
+ TSI108_STAT_RXPKTS_CARRY, &data->stats.rx_packets);
+
+ data->tmpstats.tx_packets =
+ tsi108_read_stat(data, TSI108_STAT_TXPKTS,
+ TSI108_STAT_CARRY2_TXPKTS,
+ TSI108_STAT_TXPKTS_CARRY, &data->stats.tx_packets);
+
+ data->tmpstats.rx_bytes =
+ tsi108_read_stat(data, TSI108_STAT_RXBYTES,
+ TSI108_STAT_CARRY1_RXBYTES,
+ TSI108_STAT_RXBYTES_CARRY, &data->stats.rx_bytes);
+
+ data->tmpstats.tx_bytes =
+ tsi108_read_stat(data, TSI108_STAT_TXBYTES,
+ TSI108_STAT_CARRY2_TXBYTES,
+ TSI108_STAT_TXBYTES_CARRY, &data->stats.tx_bytes);
+
+ data->tmpstats.multicast =
+ tsi108_read_stat(data, TSI108_STAT_RXMCAST,
+ TSI108_STAT_CARRY1_RXMCAST,
+ TSI108_STAT_RXMCAST_CARRY, &data->stats.multicast);
+
+ excol = tsi108_read_stat(data, TSI108_STAT_TXEXCOL,
+ TSI108_STAT_CARRY2_TXEXCOL,
+ TSI108_STAT_TXEXCOL_CARRY,
+ &data->tx_coll_abort);
+
+ data->tmpstats.collisions =
+ tsi108_read_stat(data, TSI108_STAT_TXTCOL,
+ TSI108_STAT_CARRY2_TXTCOL,
+ TSI108_STAT_TXTCOL_CARRY, &data->stats.collisions);
+
+ data->tmpstats.collisions += excol;
+
+ data->tmpstats.rx_length_errors =
+ tsi108_read_stat(data, TSI108_STAT_RXLENGTH,
+ TSI108_STAT_CARRY1_RXLENGTH,
+ TSI108_STAT_RXLENGTH_CARRY,
+ &data->stats.rx_length_errors);
+
+ data->tmpstats.rx_length_errors +=
+ tsi108_read_stat(data, TSI108_STAT_RXRUNT,
+ TSI108_STAT_CARRY1_RXRUNT,
+ TSI108_STAT_RXRUNT_CARRY, &data->rx_underruns);
+
+ data->tmpstats.rx_length_errors +=
+ tsi108_read_stat(data, TSI108_STAT_RXJUMBO,
+ TSI108_STAT_CARRY1_RXJUMBO,
+ TSI108_STAT_RXJUMBO_CARRY, &data->rx_overruns);
+
+ data->tmpstats.rx_frame_errors =
+ tsi108_read_stat(data, TSI108_STAT_RXALIGN,
+ TSI108_STAT_CARRY1_RXALIGN,
+ TSI108_STAT_RXALIGN_CARRY,
+ &data->stats.rx_frame_errors);
+
+ data->tmpstats.rx_frame_errors +=
+ tsi108_read_stat(data, TSI108_STAT_RXFCS,
+ TSI108_STAT_CARRY1_RXFCS, TSI108_STAT_RXFCS_CARRY,
+ &data->rx_fcs);
+
+ data->tmpstats.rx_frame_errors +=
+ tsi108_read_stat(data, TSI108_STAT_RXFRAG,
+ TSI108_STAT_CARRY1_RXFRAG,
+ TSI108_STAT_RXFRAG_CARRY, &data->rx_short_fcs);
+
+ data->tmpstats.rx_missed_errors =
+ tsi108_read_stat(data, TSI108_STAT_RXDROP,
+ TSI108_STAT_CARRY1_RXDROP,
+ TSI108_STAT_RXDROP_CARRY,
+ &data->stats.rx_missed_errors);
+
+ /* These three are maintained by software. */
+ data->tmpstats.rx_fifo_errors = data->stats.rx_fifo_errors;
+ data->tmpstats.rx_crc_errors = data->stats.rx_crc_errors;
+
+ data->tmpstats.tx_aborted_errors =
+ tsi108_read_stat(data, TSI108_STAT_TXEXDEF,
+ TSI108_STAT_CARRY2_TXEXDEF,
+ TSI108_STAT_TXEXDEF_CARRY,
+ &data->stats.tx_aborted_errors);
+
+ data->tmpstats.tx_aborted_errors +=
+ tsi108_read_stat(data, TSI108_STAT_TXPAUSEDROP,
+ TSI108_STAT_CARRY2_TXPAUSE,
+ TSI108_STAT_TXPAUSEDROP_CARRY,
+ &data->tx_pause_drop);
+
+ data->tmpstats.tx_aborted_errors += excol;
+
+ data->tmpstats.tx_errors = data->tmpstats.tx_aborted_errors;
+ data->tmpstats.rx_errors = data->tmpstats.rx_length_errors +
+ data->tmpstats.rx_crc_errors +
+ data->tmpstats.rx_frame_errors +
+ data->tmpstats.rx_fifo_errors + data->tmpstats.rx_missed_errors;
+
+ spin_unlock_irq(&data->misclock);
+ return &data->tmpstats;
+}
+
+static void tsi108_restart_rx(tsi108_prv_data * data, net_device * dev)
+{
+ TSI108_ETH_WRITE_REG(TSI108_EC_RXQ_PTRHIGH,
+ TSI108_EC_RXQ_PTRHIGH_VALID);
+
+ wmb();
+ TSI108_ETH_WRITE_REG(TSI108_EC_RXCTRL, TSI108_EC_RXCTRL_GO
+ | TSI108_EC_RXCTRL_QUEUE0);
+}
+
+static void tsi108_restart_tx(tsi108_prv_data * data)
+{
+ TSI108_ETH_WRITE_REG(TSI108_EC_TXQ_PTRHIGH,
+ TSI108_EC_TXQ_PTRHIGH_VALID);
+
+ wmb();
+ TSI108_ETH_WRITE_REG(TSI108_EC_TXCTRL, TSI108_EC_TXCTRL_IDLEINT |
+ TSI108_EC_TXCTRL_GO | TSI108_EC_TXCTRL_QUEUE0);
+}
+
+/* txlock must be held by caller, with IRQs disabled, and
+ * with permission to re-enable them when the lock is dropped.
+ */
+static void tsi108_check_for_completed_tx(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ int tx;
+ struct sk_buff *skb;
+ int release = 0;
+
+ while (!data->txfree || data->txhead != data->txtail) {
+ tx = data->txtail;
+
+ if (data->txring[tx].misc & TSI108_TX_OWN)
+ break;
+
+ skb = data->txskbs[tx];
+
+ if (!(data->txring[tx].misc & TSI108_TX_OK))
+ printk("%s: bad tx packet, misc %x\n",
+ dev->name, data->txring[tx].misc);
+
+ data->txtail = (data->txtail + 1) % TSI108_TXRING_LEN;
+ data->txfree++;
+
+ if (data->txring[tx].misc & TSI108_TX_EOF) {
+ dev_kfree_skb_any(skb);
+ release++;
+ }
+ }
+
+ if (release) {
+
+ if (netif_queue_stopped(dev)
+ && is_valid_ether_addr(dev->dev_addr) && data->link_up)
+ netif_wake_queue(dev);
+ }
+}
+
+static int tsi108_send_packet(sk_buff * skb, net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ int frags = skb_shinfo(skb)->nr_frags + 1;
+ int i;
+#ifdef FIXME_SG_CSUM_NOT_TESTED /* FIXME: Not supported now. */
+ long csstart;
+ long csum;
+
+ csstart = skb->len - skb->data_len;
+ if (csstart > skb->len - skb->data_len)
+ BUG();
+ csum = 0;
+ if (csstart != skb->len)
+ csum = skb_checksum(skb, csstart, skb->len - csstart, 0);
+#endif
+
+ if (!data->phy_ok && net_ratelimit())
+ printk(KERN_ERR "%s: Transmit while PHY is down!\n", dev->name);
+
+ if (!data->link_up) {
+ printk(KERN_ERR "%s: Transmit while link is down!\n",
+ dev->name);
+ netif_stop_queue(dev);
+ return 1;
+ }
+
+ if (data->txfree < MAX_SKB_FRAGS + 1) {
+ netif_stop_queue(dev);
+
+ if (net_ratelimit())
+ printk(KERN_ERR "%s: Transmit with full tx ring!\n",
+ dev->name);
+ return 1;
+ }
+
+ if (data->txfree - frags < MAX_SKB_FRAGS + 1) {
+ netif_stop_queue(dev);
+ }
+
+ spin_lock_irq(&data->txlock);
+
+ for (i = 0; i < frags; i++) {
+ int misc = 0;
+ int tx = data->txhead;
+
+ /* This is done to mark every TSI108_TX_INT_FREQ tx buffers with
+ * the interrupt bit. TX descriptor-complete interrupts are
+ * enabled when the queue fills up, and masked when there is
+ * still free space. This way, when saturating the outbound
+ * link, the tx interrupts are kept to a reasonable level.
+ * When the queue is not full, reclamation of skbs still occurs
+ * as new packets are transmitted, or on a queue-empty
+ * interrupt.
+ */
+
+ if ((tx % TSI108_TX_INT_FREQ == 0) &&
+ ((TSI108_TXRING_LEN - data->txfree) >= TSI108_TX_INT_FREQ)
+ )
+ misc = TSI108_TX_INT;
+
+ data->txskbs[tx] = skb;
+
+ if (i == 0) {
+ data->txring[tx].buf0 = virt_to_phys(skb->data);
+ data->txring[tx].len = skb->len - skb->data_len;
+ misc |= TSI108_TX_SOF;
+ } else {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i - 1];
+
+ data->txring[tx].buf0 =
+ page_to_phys(frag->page) + frag->page_offset;
+ data->txring[tx].len = frag->size;
+ }
+
+ if (i == frags - 1)
+ misc |= TSI108_TX_EOF;
+
+#ifdef TSI108_PRINT_TX_FRAME
+ {
+ int i;
+ printk("%s: Tx Frame contents (%d)\n", dev->name,
+ skb->len);
+ for (i = 0; i < skb->len; i++)
+ printk(" %2.2x", skb->data[i]);
+ printk(".\n");
+ }
+#endif /* TSI108_PRINT_TX_FRAME */
+
+ mb();
+ data->txring[tx].misc = misc | TSI108_TX_OWN;
+
+ data->txhead = (data->txhead + 1) % TSI108_TXRING_LEN;
+ data->txfree--;
+ }
+
+ tsi108_check_for_completed_tx(dev);
+
+ /* This must be done after the check for completed tx descriptors,
+ * so that the tail pointer is correct.
+ */
+
+ if (!(TSI108_ETH_READ_REG(TSI108_EC_TXSTAT) & TSI108_EC_TXSTAT_QUEUE0))
+ tsi108_restart_tx(data);
+
+ spin_unlock_irq(&data->txlock);
+ return 0;
+}
+
+static int tsi108_check_for_completed_rx(net_device * dev, int budget)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ int done = 0;
+
+ while (data->rxfree && done != budget) {
+ int rx = data->rxtail;
+ struct sk_buff *skb;
+
+ if (data->rxring[rx].misc & TSI108_RX_OWN)
+ break;
+
+ skb = data->rxskbs[rx];
+ data->rxtail = (data->rxtail + 1) % TSI108_RXRING_LEN;
+ data->rxfree--;
+ done++;
+
+ if (data->rxring[rx].misc & TSI108_RX_BAD) {
+ spin_lock_irq(&data->misclock);
+
+ if (data->rxring[rx].misc & TSI108_RX_CRC)
+ data->stats.rx_crc_errors++;
+ if (data->rxring[rx].misc & TSI108_RX_OVER)
+ data->stats.rx_fifo_errors++;
+
+ spin_unlock_irq(&data->misclock);
+
+ dev_kfree_skb_any(skb);
+ continue;
+ }
+#ifdef TSI108_PRINT_RX_FRAME
+ {
+ int i;
+ printk("%s: Rx Frame contents (%d)\n",
+ dev->name, data->rxring[rx].len);
+ for (i = 0; i < data->rxring[rx].len; i++)
+ printk(" %2.2x", skb->data[i]);
+ printk(".\n");
+ }
+#endif /* TSI108_PRINT_RX_FRAME */
+
+ skb->dev = dev;
+ skb_put(skb, data->rxring[rx].len);
+ skb->protocol = eth_type_trans(skb, dev);
+ netif_receive_skb(skb);
+ dev->last_rx = jiffies;
+ }
+
+ return done;
+}
+
+static int tsi108_refill_rx(net_device * dev, int budget)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ int done = 0;
+
+ while (data->rxfree != TSI108_RXRING_LEN && done != budget) {
+ int rx = data->rxhead;
+ sk_buff *skb;
+
+ data->rxskbs[rx] = skb = dev_alloc_skb(TSI108_RXBUF_SIZE + 2);
+ if (!skb)
+ break;
+
+ skb_reserve(skb, 2); /* Align the data on a 4-byte boundary. */
+
+ data->rxring[rx].buf0 = virt_to_phys(skb->data);
+
+ /* Sometimes the hardware sets blen to zero after packet
+ * reception, even though the manual says that it's only ever
+ * modified by the driver.
+ */
+
+ data->rxring[rx].blen = 1536;
+ mb();
+ data->rxring[rx].misc = TSI108_RX_OWN | TSI108_RX_INT;
+
+ data->rxhead = (data->rxhead + 1) % TSI108_RXRING_LEN;
+ data->rxfree++;
+ done++;
+ }
+
+ mb();
+
+ if (done != 0 && !(TSI108_ETH_READ_REG(TSI108_EC_RXSTAT) &
+ TSI108_EC_RXSTAT_QUEUE0))
+ tsi108_restart_rx(data, dev);
+
+ return done;
+}
+
+static int tsi108_poll(net_device * dev, int *budget)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ u32 estat = TSI108_ETH_READ_REG(TSI108_EC_RXESTAT);
+ u32 intstat = TSI108_ETH_READ_REG(TSI108_EC_INTSTAT);
+ int total_budget = min(*budget, dev->quota);
+ int num_received = 0, num_filled = 0, budget_used;
+
+ intstat &= TSI108_INT_RXQUEUE0 | TSI108_INT_RXTHRESH |
+ TSI108_INT_RXOVERRUN | TSI108_INT_RXERROR | TSI108_INT_RXWAIT;
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_RXESTAT, estat);
+ TSI108_ETH_WRITE_REG(TSI108_EC_INTSTAT, intstat);
+
+ if (data->rxpending || (estat & TSI108_EC_RXESTAT_Q0_DESCINT))
+ num_received = tsi108_check_for_completed_rx(dev, total_budget);
+
+ /* This should normally fill no more slots than the number of
+ * packets received in tsi108_check_for_completed_rx(). The exception
+ * is when we previously ran out of memory for RX SKBs. In that
+ * case, it's helpful to obey the budget, not only so that the
+ * CPU isn't hogged, but so that memory (which may still be low)
+ * is not hogged by one device.
+ *
+ * A work unit is considered to be two SKBs to allow us to catch
+ * up when the ring has shrunk due to out-of-memory but we're
+ * still removing the full budget's worth of packets each time.
+ */
+
+ if (data->rxfree < TSI108_RXRING_LEN)
+ num_filled = tsi108_refill_rx(dev, total_budget * 2);
+
+ if (intstat & TSI108_INT_RXERROR) {
+ u32 err = TSI108_ETH_READ_REG(TSI108_EC_RXERR);
+ TSI108_ETH_WRITE_REG(TSI108_EC_RXERR, err);
+
+ if (err) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "%s: RX error %x\n",
+ dev->name, err);
+
+ if (!(TSI108_ETH_READ_REG(TSI108_EC_RXSTAT) &
+ TSI108_EC_RXSTAT_QUEUE0))
+ tsi108_restart_rx(data, dev);
+ }
+ }
+
+ if (intstat & TSI108_INT_RXOVERRUN) {
+ spin_lock_irq(&data->misclock);
+ data->stats.rx_fifo_errors++;
+ spin_unlock_irq(&data->misclock);
+ }
+
+ budget_used = max(num_received, num_filled / 2);
+
+ *budget -= budget_used;
+ dev->quota -= budget_used;
+
+ if (budget_used != total_budget) {
+ data->rxpending = 0;
+ netif_rx_complete(dev);
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_INTMASK,
+ TSI108_ETH_READ_REG(TSI108_EC_INTMASK)
+ & ~(TSI108_INT_RXQUEUE0
+ | TSI108_INT_RXTHRESH |
+ TSI108_INT_RXOVERRUN |
+ TSI108_INT_RXERROR |
+ TSI108_INT_RXWAIT));
+
+ mb();
+
+ /* IRQs are level-triggered, so no need to re-check */
+ return 0;
+ } else {
+ data->rxpending = 1;
+ }
+
+ return 1;
+}
+
+static void tsi108_rx_int(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+
+ /* A race could cause dev to already be scheduled, so it's not an
+ * error if that happens (and interrupts shouldn't be re-masked,
+ * because that can cause harmful races, if poll has already
+ * unmasked them but not cleared LINK_STATE_SCHED).
+ *
+ * This can happen if this code races with tsi108_poll(), which masks
+ * the interrupts after tsi108_irq_one() read the mask, but before
+ * netif_rx_schedule is called. It could also happen due to calls
+ * from tsi108_check_rxring().
+ */
+
+ if (netif_rx_schedule_prep(dev)) {
+ /* Mask, rather than ack, the receive interrupts. The ack
+ * will happen in tsi108_poll().
+ */
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_INTMASK,
+ TSI108_ETH_READ_REG(TSI108_EC_INTMASK) |
+ TSI108_INT_RXQUEUE0
+ | TSI108_INT_RXTHRESH |
+ TSI108_INT_RXOVERRUN | TSI108_INT_RXERROR |
+ TSI108_INT_RXWAIT);
+ mb();
+ __netif_rx_schedule(dev);
+ } else {
+ if (!netif_running(dev)) {
+ /* This can happen if an interrupt occurs while the
+ * interface is being brought down, as the START
+ * bit is cleared before the stop function is called.
+ *
+ * In this case, the interrupts must be masked, or
+ * they will continue indefinitely.
+ *
+ * There's a race here if the interface is brought down
+ * and then up in rapid succession, as the device could
+ * be made running after the above check and before
+ * the masking below. This will only happen if the IRQ
+ * thread has a lower priority than the task brining
+ * up the interface. Fixing this race would likely
+ * require changes in generic code.
+ */
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_INTMASK,
+ TSI108_ETH_READ_REG
+ (TSI108_EC_INTMASK) |
+ TSI108_INT_RXQUEUE0 |
+ TSI108_INT_RXTHRESH |
+ TSI108_INT_RXOVERRUN |
+ TSI108_INT_RXERROR |
+ TSI108_INT_RXWAIT);
+ mb();
+ }
+ }
+}
+
+/* If the RX ring has run out of memory, try periodically
+ * to allocate some more, as otherwise poll would never
+ * get called (apart from the initial end-of-queue condition).
+ *
+ * This is called once per second (by default) from the thread.
+ */
+
+static void tsi108_check_rxring(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+
+ /* A poll is scheduled, as opposed to caling tsi108_refill_rx
+ * directly, so as to keep the receive path single-threaded
+ * (and thus not needing a lock).
+ */
+
+ if (netif_running(dev) && data->rxfree < TSI108_RXRING_LEN / 4)
+ tsi108_rx_int(dev);
+}
+
+static void tsi108_tx_int(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ u32 estat = TSI108_ETH_READ_REG(TSI108_EC_TXESTAT);
+
+ mb();
+ TSI108_ETH_WRITE_REG(TSI108_EC_TXESTAT, estat);
+ mb();
+ TSI108_ETH_WRITE_REG(TSI108_EC_INTSTAT, TSI108_INT_TXQUEUE0 |
+ TSI108_INT_TXIDLE | TSI108_INT_TXERROR);
+ mb();
+ if (estat & TSI108_EC_TXESTAT_Q0_ERR) {
+ u32 err = TSI108_ETH_READ_REG(TSI108_EC_TXERR);
+ TSI108_ETH_WRITE_REG(TSI108_EC_TXERR, err);
+
+ if (err && net_ratelimit())
+ printk(KERN_ERR "%s: TX error %x\n", dev->name, err);
+ }
+
+ if (estat & (TSI108_EC_TXESTAT_Q0_DESCINT | TSI108_EC_TXESTAT_Q0_EOQ)) {
+ spin_lock(&data->txlock);
+ tsi108_check_for_completed_tx(dev);
+ spin_unlock(&data->txlock);
+ }
+}
+
+static irqreturn_t tsi108_irq_one(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ u32 stat = TSI108_ETH_READ_REG(TSI108_EC_INTSTAT);
+
+ if (!(stat & TSI108_INT_ANY))
+ return IRQ_NONE; /* Not our interrupt */
+
+ stat &= ~TSI108_ETH_READ_REG(TSI108_EC_INTMASK);
+
+ if (stat & (TSI108_INT_TXQUEUE0 | TSI108_INT_TXIDLE |
+ TSI108_INT_TXERROR))
+ tsi108_tx_int(dev);
+ if (stat & (TSI108_INT_RXQUEUE0 | TSI108_INT_RXTHRESH |
+ TSI108_INT_RXWAIT | TSI108_INT_RXOVERRUN |
+ TSI108_INT_RXERROR))
+ tsi108_rx_int(dev);
+
+ if (stat & TSI108_INT_SFN) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "%s: SFN error\n", dev->name);
+ TSI108_ETH_WRITE_REG(TSI108_EC_INTSTAT, TSI108_INT_SFN);
+ }
+
+ if (stat & TSI108_INT_STATCARRY) {
+ tsi108_stat_carry(dev);
+ TSI108_ETH_WRITE_REG(TSI108_EC_INTSTAT, TSI108_INT_STATCARRY);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t tsi108_irq(int irq, void *dev_id, struct pt_regs *regs)
+{
+ if ((IRQ_TSI108_GIGE0 != irq) && (IRQ_TSI108_GIGE1 != irq))
+ return IRQ_NONE; /* Not our interrupt */
+
+ return tsi108_irq_one((struct net_device *)dev_id);
+}
+
+static void tsi108_stop_ethernet(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+
+ /* Disable all TX and RX queues ... */
+ TSI108_ETH_WRITE_REG(TSI108_EC_TXCTRL, 0);
+ TSI108_ETH_WRITE_REG(TSI108_EC_RXCTRL, 0);
+
+ /* ...and wait for them to become idle */
+ mb();
+ while (TSI108_ETH_READ_REG(TSI108_EC_TXSTAT) &
+ TSI108_EC_TXSTAT_ACTIVE) ;
+ while (TSI108_ETH_READ_REG(TSI108_EC_RXSTAT) &
+ TSI108_EC_RXSTAT_ACTIVE) ;
+}
+
+static void tsi108_reset_ether(tsi108_prv_data * data)
+{
+ TSI108_ETH_WRITE_REG(TSI108_MAC_CFG1, TSI108_MAC_CFG1_SOFTRST);
+ udelay(100);
+ TSI108_ETH_WRITE_REG(TSI108_MAC_CFG1, 0);
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_PORTCTRL, TSI108_EC_PORTCTRL_STATRST);
+ udelay(100);
+ TSI108_ETH_WRITE_REG(TSI108_EC_PORTCTRL,
+ TSI108_ETH_READ_REG(TSI108_EC_PORTCTRL) &
+ ~TSI108_EC_PORTCTRL_STATRST);
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_TXCFG, TSI108_EC_TXCFG_RST);
+ udelay(100);
+ TSI108_ETH_WRITE_REG(TSI108_EC_TXCFG,
+ TSI108_ETH_READ_REG(TSI108_EC_TXCFG) &
+ ~TSI108_EC_TXCFG_RST);
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_RXCFG, TSI108_EC_RXCFG_RST);
+ udelay(100);
+ TSI108_ETH_WRITE_REG(TSI108_EC_RXCFG,
+ TSI108_ETH_READ_REG(TSI108_EC_RXCFG) &
+ ~TSI108_EC_RXCFG_RST);
+
+ TSI108_ETH_WRITE_REG(TSI108_MAC_MII_MGMT_CFG,
+ TSI108_ETH_READ_REG(TSI108_MAC_MII_MGMT_CFG) |
+ TSI108_MAC_MII_MGMT_RST);
+ udelay(100);
+ TSI108_ETH_WRITE_REG(TSI108_MAC_MII_MGMT_CFG,
+ TSI108_ETH_READ_REG(TSI108_MAC_MII_MGMT_CFG) &
+ ~(TSI108_MAC_MII_MGMT_RST |
+ TSI108_MAC_MII_MGMT_CLK));
+
+ TSI108_ETH_WRITE_REG(TSI108_MAC_MII_MGMT_CFG,
+ TSI108_ETH_READ_REG(TSI108_MAC_MII_MGMT_CFG) |
+ TSI108_MAC_MII_MGMT_CLK);
+}
+
+static int tsi108_get_mac(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+
+ u32 word1 = TSI108_ETH_READ_REG(TSI108_MAC_ADDR1);
+ u32 word2 = TSI108_ETH_READ_REG(TSI108_MAC_ADDR2);
+
+ /* Note that the octets are reversed from what the manual says,
+ * producing an even weirder ordering...
+ */
+ if (word2 == 0 && word1 == 0) {
+ dev->dev_addr[0] = 0x00;
+ dev->dev_addr[1] = 0x06;
+ dev->dev_addr[2] = 0xd2;
+ dev->dev_addr[3] = 0x00;
+ dev->dev_addr[4] = 0x00;
+ if (0x8 == data->phy)
+ dev->dev_addr[5] = 0x01;
+ else
+ dev->dev_addr[5] = 0x02;
+
+ word2 = (dev->dev_addr[0] << 16) | (dev->dev_addr[1] << 24);
+
+ word1 = (dev->dev_addr[2] << 0) | (dev->dev_addr[3] << 8) |
+ (dev->dev_addr[4] << 16) | (dev->dev_addr[5] << 24);
+
+ TSI108_ETH_WRITE_REG(TSI108_MAC_ADDR1, word1);
+ TSI108_ETH_WRITE_REG(TSI108_MAC_ADDR2, word2);
+ } else {
+ dev->dev_addr[0] = (word2 >> 16) & 0xff;
+ dev->dev_addr[1] = (word2 >> 24) & 0xff;
+ dev->dev_addr[2] = (word1 >> 0) & 0xff;
+ dev->dev_addr[3] = (word1 >> 8) & 0xff;
+ dev->dev_addr[4] = (word1 >> 16) & 0xff;
+ dev->dev_addr[5] = (word1 >> 24) & 0xff;
+ }
+
+ if (!is_valid_ether_addr(dev->dev_addr)) {
+ printk("KERN_ERR: word1: %08x, word2: %08x\n", word1, word2);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int tsi108_set_mac(net_device * dev, void *addr)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ u32 word1, word2;
+ int i;
+
+ if (!is_valid_ether_addr(addr))
+ return -EINVAL;
+
+ for (i = 0; i < 6; i++)
+ /* +2 is for the offset of the HW addr type */
+ dev->dev_addr[i] = ((unsigned char *)addr)[i + 2];
+
+ word2 = (dev->dev_addr[0] << 16) | (dev->dev_addr[1] << 24);
+
+ word1 = (dev->dev_addr[2] << 0) | (dev->dev_addr[3] << 8) |
+ (dev->dev_addr[4] << 16) | (dev->dev_addr[5] << 24);
+
+ spin_lock_irq(&data->misclock);
+ TSI108_ETH_WRITE_REG(TSI108_MAC_ADDR1, word1);
+ TSI108_ETH_WRITE_REG(TSI108_MAC_ADDR2, word2);
+ spin_lock(&data->txlock);
+
+ if (netif_queue_stopped(dev) && data->txfree && data->link_up)
+ netif_wake_queue(dev);
+
+ spin_unlock(&data->txlock);
+ spin_unlock_irq(&data->misclock);
+ return 0;
+}
+
+/* Protected by dev->xmit_lock. */
+static void tsi108_set_rx_mode(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ u32 rxcfg = TSI108_ETH_READ_REG(TSI108_EC_RXCFG);
+
+ if (dev->flags & IFF_PROMISC) {
+ rxcfg &= ~(TSI108_EC_RXCFG_UC_HASH | TSI108_EC_RXCFG_MC_HASH);
+ rxcfg |= TSI108_EC_RXCFG_UFE | TSI108_EC_RXCFG_MFE;
+ goto out;
+ }
+
+ rxcfg &= ~(TSI108_EC_RXCFG_UFE | TSI108_EC_RXCFG_MFE);
+
+ if (dev->mc_count) {
+ int i;
+ struct dev_mc_list *mc = dev->mc_list;
+ rxcfg |= TSI108_EC_RXCFG_MFE | TSI108_EC_RXCFG_MC_HASH;
+
+ memset(data->mc_hash, 0, sizeof(data->mc_hash));
+
+ while (mc) {
+ u32 hash, crc;
+
+ if (mc->dmi_addrlen == 6) {
+ crc = ether_crc(6, mc->dmi_addr);
+ hash = crc >> 23;
+
+ __set_bit(hash, &data->mc_hash[0]);
+ } else {
+ printk(KERN_ERR
+ "%s: got multicast address of length %d "
+ "instead of 6.\n", dev->name,
+ mc->dmi_addrlen);
+ }
+
+ mc = mc->next;
+ }
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_HASHADDR,
+ TSI108_EC_HASHADDR_AUTOINC |
+ TSI108_EC_HASHADDR_MCAST);
+
+ for (i = 0; i < 16; i++) {
+ /* The manual says that the hardware may drop
+ * back-to-back writes to the data register.
+ */
+ udelay(1);
+ mb();
+ TSI108_ETH_WRITE_REG(TSI108_EC_HASHDATA,
+ data->mc_hash[i]);
+ mb();
+ }
+ }
+
+ out:
+ mb();
+ TSI108_ETH_WRITE_REG(TSI108_EC_RXCFG, rxcfg);
+}
+
+static void tsi108_init_phy(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ u32 i = 0;
+ u16 phyVal = 0;
+
+ spin_lock_irq(&phy_lock);
+
+ tsi108_write_mii(data, PHY_CTRL, PHY_CTRL_RESET);
+ mb();
+ while (tsi108_read_mii(data, PHY_CTRL, NULL) & PHY_CTRL_RESET) ;
+
+#if (TSI108_PHY_TYPE == PHY_BCM54XX) /* Broadcom BCM54xx PHY */
+ tsi108_write_mii(data, 0x09, 0x0300);
+ tsi108_write_mii(data, 0x10, 0x1020);
+ tsi108_write_mii(data, 0x1c, 0x8c00);
+ mb();
+#endif
+
+ tsi108_write_mii(data,
+ PHY_CTRL,
+ PHY_CTRL_AUTONEG_EN | PHY_CTRL_AUTONEG_START);
+ mb();
+ while (tsi108_read_mii(data, PHY_CTRL, NULL) & PHY_CTRL_AUTONEG_START) ;
+
+ /* Set G/MII mode and receive clock select in TBI control #2. The
+ * second port won't work if this isn't done, even though we don't
+ * use TBI mode.
+ */
+
+ tsi108_write_tbi(data, 0x11, 0x30);
+
+ /* FIXME: It seems to take more than 2 back-to-back reads to the
+ * PHY_STAT register before the link up status bit is set.
+ */
+
+ data->link_up = 1;
+
+ while (!((phyVal = tsi108_read_mii(data, PHY_STAT, NULL)) &
+ PHY_STAT_LINKUP)) {
+ if (i++ > (MII_READ_DELAY / 10)) {
+ data->link_up = 0;
+ break;
+ }
+ mdelay(10);
+ }
+
+ printk(KERN_DEBUG "PHY_STAT reg contains %08x\n", phyVal);
+ data->phy_ok = 1;
+ spin_unlock_irq(&phy_lock);
+}
+
+static void tsi108_kill_phy(struct net_device *dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+
+ spin_lock_irq(&phy_lock);
+ tsi108_write_mii(data, PHY_CTRL, PHY_CTRL_POWERDOWN);
+ data->phy_ok = 0;
+ spin_unlock_irq(&phy_lock);
+}
+
+static int tsi108_open(struct net_device *dev)
+{
+ int i;
+ tsi108_prv_data *data = netdev_priv(dev);
+ unsigned int rxring_size = TSI108_RXRING_LEN * sizeof(rx_desc);
+ unsigned int txring_size = TSI108_TXRING_LEN * sizeof(tx_desc);
+
+ printk(KERN_DEBUG "Inside tsi108_open()!\n");
+
+ i = request_irq(data->irq_num, tsi108_irq, 0, dev->name, dev);
+ if (i != 0) {
+ printk(KERN_ERR "tsi108_eth%d: Could not allocate IRQ%d.\n",
+ data->irq_num % IRQ_TSI108_GIGE0, data->irq_num);
+ return i;
+ } else {
+ dev->irq = data->irq_num;
+ printk(KERN_NOTICE
+ "tsi108_open : Port %d Assigned IRQ %d to %s\n",
+ data->irq_num % IRQ_TSI108_GIGE0, dev->irq, dev->name);
+ }
+
+ data->rxring = pci_alloc_consistent(NULL, rxring_size, &data->rxdma);
+
+ if (!data->rxring) {
+ printk(KERN_DEBUG
+ "TSI108_ETH: failed to allocate memory for rxring!\n");
+ return -ENOMEM;
+ } else {
+ memset(data->rxring, 0, rxring_size);
+ }
+
+ data->txring = pci_alloc_consistent(NULL, txring_size, &data->txdma);
+
+ if (!data->txring) {
+ printk(KERN_DEBUG
+ "TSI108_ETH: failed to allocate memory for txring!\n");
+ pci_free_consistent(0, rxring_size, data->rxring, data->rxdma);
+ return -ENOMEM;
+ } else {
+ memset(data->txring, 0, txring_size);
+ }
+
+ for (i = 0; i < TSI108_RXRING_LEN; i++) {
+ data->rxring[i].next0 = data->rxdma + (i + 1) * sizeof(rx_desc);
+ data->rxring[i].blen = TSI108_RXBUF_SIZE;
+ data->rxring[i].vlan = 0;
+ }
+
+ data->rxring[TSI108_RXRING_LEN - 1].next0 = data->rxdma;
+
+ data->rxtail = 0;
+ data->rxhead = 0;
+
+ for (i = 0; i < TSI108_RXRING_LEN; i++) {
+ sk_buff *skb = dev_alloc_skb(TSI108_RXBUF_SIZE + NET_IP_ALIGN);
+
+ if (!skb) {
+ /* Bah. No memory for now, but maybe we'll get
+ * some more later.
+ * For now, we'll live with the smaller ring.
+ */
+ printk(KERN_WARNING
+ "%s: Could only allocate %d receive skb(s).\n",
+ dev->name, i);
+ data->rxhead = i;
+ break;
+ }
+
+ data->rxskbs[i] = skb;
+ /* Align the payload on a 4-byte boundary */
+ skb_reserve(skb, 2);
+ data->rxskbs[i] = skb;
+ data->rxring[i].buf0 = virt_to_phys(data->rxskbs[i]->data);
+ data->rxring[i].misc = TSI108_RX_OWN | TSI108_RX_INT;
+ }
+
+ data->rxfree = i;
+ TSI108_ETH_WRITE_REG(TSI108_EC_RXQ_PTRLOW, data->rxdma);
+
+ for (i = 0; i < TSI108_TXRING_LEN; i++) {
+ data->txring[i].next0 = data->txdma + (i + 1) * sizeof(tx_desc);
+ data->txring[i].misc = 0;
+ }
+
+ data->txring[TSI108_TXRING_LEN - 1].next0 = data->txdma;
+ data->txtail = 0;
+ data->txhead = 0;
+ data->txfree = TSI108_TXRING_LEN;
+ TSI108_ETH_WRITE_REG(TSI108_EC_TXQ_PTRLOW, data->txdma);
+ tsi108_init_phy(dev);
+
+ init_timer(&data->timer);
+ data->timer.expires = jiffies + 1;
+ data->timer.data = (unsigned long)dev;
+ data->timer.function = &tsi108_timed_checker; /* timer handler */
+ add_timer(&data->timer);
+
+ tsi108_restart_rx(data, dev);
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_INTSTAT, ~0);
+ mb();
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_INTMASK,
+ ~(TSI108_INT_TXQUEUE0 | TSI108_INT_RXERROR |
+ TSI108_INT_RXTHRESH | TSI108_INT_RXQUEUE0 |
+ TSI108_INT_RXOVERRUN | TSI108_INT_RXWAIT |
+ TSI108_INT_SFN | TSI108_INT_STATCARRY));
+
+ TSI108_ETH_WRITE_REG(TSI108_MAC_CFG1,
+ TSI108_MAC_CFG1_RXEN | TSI108_MAC_CFG1_TXEN);
+ netif_start_queue(dev);
+ return 0;
+}
+
+static int tsi108_close(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+
+ netif_stop_queue(dev);
+
+ del_timer_sync(&data->timer);
+
+ printk(KERN_DEBUG "Inside tsi108_ifdown!\n");
+
+ tsi108_stop_ethernet(dev);
+ tsi108_kill_phy(dev);
+ TSI108_ETH_WRITE_REG(TSI108_EC_INTMASK, ~0);
+ TSI108_ETH_WRITE_REG(TSI108_MAC_CFG1, 0);
+
+ /* Check for any pending TX packets, and drop them. */
+
+ while (!data->txfree || data->txhead != data->txtail) {
+ int tx = data->txtail;
+ struct sk_buff *skb;
+ skb = data->txskbs[tx];
+ data->txtail = (data->txtail + 1) % TSI108_TXRING_LEN;
+ data->txfree++;
+ dev_kfree_skb(skb);
+ }
+
+ synchronize_irq(data->irq_num);
+ free_irq(data->irq_num, dev);
+
+ /* Discard the RX ring. */
+
+ while (data->rxfree) {
+ int rx = data->rxtail;
+ struct sk_buff *skb;
+
+ skb = data->rxskbs[rx];
+ data->rxtail = (data->rxtail + 1) % TSI108_RXRING_LEN;
+ data->rxfree--;
+ dev_kfree_skb(skb);
+ }
+
+ pci_free_consistent(0,
+ TSI108_RXRING_LEN * sizeof(rx_desc),
+ data->rxring, data->rxdma);
+ pci_free_consistent(0,
+ TSI108_TXRING_LEN * sizeof(tx_desc),
+ data->txring, data->txdma);
+
+ return 0;
+}
+
+static void tsi108_init_mac(net_device * dev)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+
+ TSI108_ETH_WRITE_REG(TSI108_MAC_CFG2, TSI108_MAC_CFG2_DFLT_PREAMBLE |
+ TSI108_MAC_CFG2_PADCRC);
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_TXTHRESH,
+ (192 << TSI108_EC_TXTHRESH_STARTFILL) |
+ (192 << TSI108_EC_TXTHRESH_STOPFILL));
+
+ TSI108_ETH_WRITE_REG(TSI108_STAT_CARRYMASK1,
+ ~(TSI108_STAT_CARRY1_RXBYTES |
+ TSI108_STAT_CARRY1_RXPKTS |
+ TSI108_STAT_CARRY1_RXFCS |
+ TSI108_STAT_CARRY1_RXMCAST |
+ TSI108_STAT_CARRY1_RXALIGN |
+ TSI108_STAT_CARRY1_RXLENGTH |
+ TSI108_STAT_CARRY1_RXRUNT |
+ TSI108_STAT_CARRY1_RXJUMBO |
+ TSI108_STAT_CARRY1_RXFRAG |
+ TSI108_STAT_CARRY1_RXJABBER |
+ TSI108_STAT_CARRY1_RXDROP));
+
+ TSI108_ETH_WRITE_REG(TSI108_STAT_CARRYMASK2,
+ ~(TSI108_STAT_CARRY2_TXBYTES |
+ TSI108_STAT_CARRY2_TXPKTS |
+ TSI108_STAT_CARRY2_TXEXDEF |
+ TSI108_STAT_CARRY2_TXEXCOL |
+ TSI108_STAT_CARRY2_TXTCOL |
+ TSI108_STAT_CARRY2_TXPAUSE));
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_PORTCTRL, TSI108_EC_PORTCTRL_STATEN);
+ TSI108_ETH_WRITE_REG(TSI108_MAC_CFG1, 0);
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_RXCFG,
+ TSI108_EC_RXCFG_SE | TSI108_EC_RXCFG_BFE);
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_TXQ_CFG, TSI108_EC_TXQ_CFG_DESC_INT |
+ TSI108_EC_TXQ_CFG_EOQ_OWN_INT |
+ TSI108_EC_TXQ_CFG_WSWP | (TSI108_PBM_PORT <<
+ TSI108_EC_TXQ_CFG_SFNPORT));
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_RXQ_CFG, TSI108_EC_RXQ_CFG_DESC_INT |
+ TSI108_EC_RXQ_CFG_EOQ_OWN_INT |
+ TSI108_EC_RXQ_CFG_WSWP | (TSI108_PBM_PORT <<
+ TSI108_EC_RXQ_CFG_SFNPORT));
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_TXQ_BUFCFG,
+ TSI108_EC_TXQ_BUFCFG_BURST256 |
+ TSI108_EC_TXQ_BUFCFG_BSWP | (TSI108_PBM_PORT <<
+ TSI108_EC_TXQ_BUFCFG_SFNPORT));
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_RXQ_BUFCFG,
+ TSI108_EC_RXQ_BUFCFG_BURST256 |
+ TSI108_EC_RXQ_BUFCFG_BSWP | (TSI108_PBM_PORT <<
+ TSI108_EC_RXQ_BUFCFG_SFNPORT));
+
+ TSI108_ETH_WRITE_REG(TSI108_EC_INTMASK, ~0);
+}
+
+static int tsi108_ioctl(net_device * dev, struct ifreq *rq, int cmd)
+{
+ tsi108_prv_data *data = netdev_priv(dev);
+ struct mii_ioctl_data *mii_data =
+ (struct mii_ioctl_data *)&rq->ifr_data;
+ int ret;
+
+ switch (cmd) {
+ case SIOCGMIIPHY:
+ mii_data->phy_id = data->phy;
+ ret = 0;
+ break;
+
+ case SIOCGMIIREG:
+ spin_lock_irq(&phy_lock);
+ mii_data->val_out =
+ tsi108_read_mii(data, mii_data->reg_num, &ret);
+ spin_unlock_irq(&phy_lock);
+ break;
+
+ default:
+ ret = -EOPNOTSUPP;
+ }
+
+ return ret;
+}
+
+static int
+tsi108_init_one(unsigned long regs, unsigned long phyregs, u16 phy, u16 irq_num)
+{
+ net_device *dev = alloc_etherdev(sizeof(tsi108_prv_data));
+ tsi108_prv_data *data;
+ int ret;
+
+ if (!dev) {
+ printk("tsi108_eth: Could not allocate a device structure\n");
+ return -ENOMEM;
+ }
+
+ data = netdev_priv(dev);
+ memset(data, 0, sizeof(tsi108_prv_data));
+
+ data->regs = (volatile u32)regs;
+ data->phyregs = (volatile u32)phyregs;
+ data->phy = phy;
+ data->irq_num = irq_num;
+
+ dev->open = tsi108_open;
+ dev->stop = tsi108_close;
+ dev->hard_start_xmit = tsi108_send_packet;
+ dev->set_mac_address = tsi108_set_mac;
+ dev->set_multicast_list = tsi108_set_rx_mode;
+ dev->get_stats = tsi108_get_stats;
+ dev->poll = tsi108_poll;
+ dev->do_ioctl = tsi108_ioctl;
+ dev->weight = 64; /* 64 is more suitable for GigE interface - klai */
+
+ /* Apparently, the Linux networking code won't use scatter-gather
+ * if the hardware doesn't do checksums. However, it's faster
+ * to checksum in place and use SG, as (among other reasons)
+ * the cache won't be dirtied (which then has to be flushed
+ * before DMA). The checksumming is done by the driver (via
+ * a new function skb_csum_dev() in net/core/skbuff.c).
+ */
+
+#ifdef FIXME_SG_CSUM_NOT_TESTED /* FIXME: Not supported now. */
+ dev->features = NETIF_F_HIGHDMA | NETIF_F_SG | NETIF_F_HW_CSUM;
+#else
+ dev->features = NETIF_F_HIGHDMA;
+#endif
+ SET_MODULE_OWNER(dev);
+
+ spin_lock_init(&data->txlock);
+ spin_lock_init(&data->misclock);
+
+ tsi108_reset_ether(data);
+ tsi108_kill_phy(dev);
+
+ if (tsi108_get_mac(dev) != 0)
+ printk(KERN_ERR "%s: Invalid MAC address. Please correct.\n",
+ dev->name);
+
+ tsi108_init_mac(dev);
+
+ tsi108_devs[irq_num % IRQ_TSI108_GIGE0] = dev;
+
+ ret = register_netdev(dev);
+ if (ret < 0) {
+ kfree(dev);
+ return ret;
+ }
+
+ printk(KERN_INFO "%s: Tsi108 Gigabit Ethernet, MAC: "
+ "%02x:%02x:%02x:%02x:%02x:%02x\n", dev->name,
+ dev->dev_addr[0], dev->dev_addr[1], dev->dev_addr[2],
+ dev->dev_addr[3], dev->dev_addr[4], dev->dev_addr[5]);
+
+ return 0;
+}
+
+/* There's no way to either get interrupts from the PHY when
+ * something changes, or to have the Tsi108 automatically communicate
+ * with the PHY to reconfigure itself.
+ *
+ * Thus, we have to do it using a timer.
+ */
+
+static void tsi108_timed_checker(unsigned long dev_ptr)
+{
+ struct net_device *dev = (struct net_device *)dev_ptr;
+ tsi108_prv_data *data = netdev_priv(dev);
+
+ tsi108_check_phy(dev);
+ tsi108_check_rxring(dev);
+ data->timer.expires = jiffies + CHECK_PHY_INTERVAL;
+ add_timer(&data->timer);
+}
+
+static int tsi108_ether_init(void)
+{
+ int ret;
+ int dev_count = 0;
+ int i;
+
+ hw_info_table[0].regs = (u32) ioremap(hw_info_table[0].regs, 0x400);
+ hw_info_table[0].phyregs = hw_info_table[0].regs;
+
+ hw_info_table[1].regs = (u32) ioremap(hw_info_table[1].regs, 0x400);
+ hw_info_table[1].phyregs = hw_info_table[0].regs;
+
+ for (i = 0; hw_info_table[i].regs != TBL_END; i++) {
+ ret = tsi108_init_one(hw_info_table[i].regs,
+ hw_info_table[i].phyregs,
+ hw_info_table[i].phy,
+ hw_info_table[i].irq_num);
+ if (ret < 0)
+ printk("tsi108_ether_init: error initializing ethernet "
+ "device%d\n", i);
+ else
+ dev_count++;
+ }
+
+ printk("tsi108_ether_init: found %d device(s)\n", dev_count);
+
+ return 0;
+}
+
+static void tsi108_ether_exit(void)
+{
+ int i;
+ net_device *dev;
+
+ for (i = 0; hw_info_table[i].regs != TBL_END; i++) {
+ if ((dev = tsi108_devs[i]) != NULL) {
+ unregister_netdev(dev);
+ tsi108_stop_ethernet(dev);
+ kfree(dev);
+ tsi108_devs[i] = NULL;
+ }
+ }
+}
+
+module_init(tsi108_ether_init);
+module_exit(tsi108_ether_exit);
+
+MODULE_AUTHOR("Tundra Semiconductor Corporation");
+MODULE_DESCRIPTION("Tsi108 Gigabit Ethernet driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/net/tsi108_eth.h b/drivers/net/tsi108_eth.h
new file mode 100644
index 0000000..cb54f92
--- /dev/null
+++ b/drivers/net/tsi108_eth.h
@@ -0,0 +1,404 @@
+/*
+ * (C) Copyright 2005 Tundra Semiconductor Corp.
+ * Kong Lai, <kong.lai@tundra.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
+ */
+
+/*
+ * net/tsi108_eth.h - definitions for Tsi108 GIGE network controller.
+ */
+
+#ifndef __TSI108_ETH_H
+#define __TSI108_ETH_H
+
+#include <linux/config.h>
+#include <linux/types.h>
+
+#define TSI108_ETH_WRITE_REG(offset, val) \
+ out_be32((volatile u32 *)(data->regs + offset), val)
+
+#define TSI108_ETH_READ_REG(offset) \
+ in_be32((volatile u32 *)(data->regs + offset))
+
+#define TSI108_ETH_WRITE_PHYREG(offset, val) \
+ out_be32((volatile u32 *)(data->phyregs + offset), val)
+
+#define TSI108_ETH_READ_PHYREG(offset) \
+ in_be32((volatile u32 *)(data->phyregs + offset))
+
+/*
+ * PHY Configuration Options
+ *
+ * NOTE: Enable set of definitions corresponding to your board type
+ */
+#define PHY_MV88E 1 /* Marvel 88Exxxx PHY */
+#define PHY_BCM54XX 2 /* Broardcom BCM54xx PHY */
+#define TSI108_PHY_TYPE PHY_MV88E
+
+/*
+ * TSI108 GIGE port registers
+ */
+
+#define TSI108_ETH_PORT_NUM 2
+#define TSI108_PBM_PORT 2
+#define TSI108_SDRAM_PORT 4
+
+#define TSI108_MAC_CFG1 (0x000)
+#define TSI108_MAC_CFG1_SOFTRST (1 << 31)
+#define TSI108_MAC_CFG1_LOOPBACK (1 << 8)
+#define TSI108_MAC_CFG1_RXEN (1 << 2)
+#define TSI108_MAC_CFG1_TXEN (1 << 0)
+
+#define TSI108_MAC_CFG2 (0x004)
+#define TSI108_MAC_CFG2_DFLT_PREAMBLE (7 << 12)
+#define TSI108_MAC_CFG2_IFACE_MASK (3 << 8)
+#define TSI108_MAC_CFG2_NOGIG (1 << 8)
+#define TSI108_MAC_CFG2_GIG (2 << 8)
+#define TSI108_MAC_CFG2_PADCRC (1 << 2)
+#define TSI108_MAC_CFG2_FULLDUPLEX (1 << 0)
+
+#define TSI108_MAC_MII_MGMT_CFG (0x020)
+#define TSI108_MAC_MII_MGMT_CLK (7 << 0)
+#define TSI108_MAC_MII_MGMT_RST (1 << 31)
+
+#define TSI108_MAC_MII_CMD (0x024)
+#define TSI108_MAC_MII_CMD_READ (1 << 0)
+
+#define TSI108_MAC_MII_ADDR (0x028)
+#define TSI108_MAC_MII_ADDR_REG 0
+#define TSI108_MAC_MII_ADDR_PHY 8
+
+#define TSI108_MAC_MII_DATAOUT (0x02c)
+#define TSI108_MAC_MII_DATAIN (0x030)
+
+#define TSI108_MAC_MII_IND (0x034)
+#define TSI108_MAC_MII_IND_NOTVALID (1 << 2)
+#define TSI108_MAC_MII_IND_SCANNING (1 << 1)
+#define TSI108_MAC_MII_IND_BUSY (1 << 0)
+
+#define TSI108_MAC_IFCTRL (0x038)
+#define TSI108_MAC_IFCTRL_PHYMODE (1 << 24)
+
+#define TSI108_MAC_ADDR1 (0x040)
+#define TSI108_MAC_ADDR2 (0x044)
+
+#define TSI108_STAT_RXBYTES (0x06c)
+#define TSI108_STAT_RXBYTES_CARRY (1 << 24)
+
+#define TSI108_STAT_RXPKTS (0x070)
+#define TSI108_STAT_RXPKTS_CARRY (1 << 18)
+
+#define TSI108_STAT_RXFCS (0x074)
+#define TSI108_STAT_RXFCS_CARRY (1 << 12)
+
+#define TSI108_STAT_RXMCAST (0x078)
+#define TSI108_STAT_RXMCAST_CARRY (1 << 18)
+
+#define TSI108_STAT_RXALIGN (0x08c)
+#define TSI108_STAT_RXALIGN_CARRY (1 << 12)
+
+#define TSI108_STAT_RXLENGTH (0x090)
+#define TSI108_STAT_RXLENGTH_CARRY (1 << 12)
+
+#define TSI108_STAT_RXRUNT (0x09c)
+#define TSI108_STAT_RXRUNT_CARRY (1 << 12)
+
+#define TSI108_STAT_RXJUMBO (0x0a0)
+#define TSI108_STAT_RXJUMBO_CARRY (1 << 12)
+
+#define TSI108_STAT_RXFRAG (0x0a4)
+#define TSI108_STAT_RXFRAG_CARRY (1 << 12)
+
+#define TSI108_STAT_RXJABBER (0x0a8)
+#define TSI108_STAT_RXJABBER_CARRY (1 << 12)
+
+#define TSI108_STAT_RXDROP (0x0ac)
+#define TSI108_STAT_RXDROP_CARRY (1 << 12)
+
+#define TSI108_STAT_TXBYTES (0x0b0)
+#define TSI108_STAT_TXBYTES_CARRY (1 << 24)
+
+#define TSI108_STAT_TXPKTS (0x0b4)
+#define TSI108_STAT_TXPKTS_CARRY (1 << 18)
+
+#define TSI108_STAT_TXEXDEF (0x0c8)
+#define TSI108_STAT_TXEXDEF_CARRY (1 << 12)
+
+#define TSI108_STAT_TXEXCOL (0x0d8)
+#define TSI108_STAT_TXEXCOL_CARRY (1 << 12)
+
+#define TSI108_STAT_TXTCOL (0x0dc)
+#define TSI108_STAT_TXTCOL_CARRY (1 << 13)
+
+#define TSI108_STAT_TXPAUSEDROP (0x0e4)
+#define TSI108_STAT_TXPAUSEDROP_CARRY (1 << 12)
+
+#define TSI108_STAT_CARRY1 (0x100)
+#define TSI108_STAT_CARRY1_RXBYTES (1 << 16)
+#define TSI108_STAT_CARRY1_RXPKTS (1 << 15)
+#define TSI108_STAT_CARRY1_RXFCS (1 << 14)
+#define TSI108_STAT_CARRY1_RXMCAST (1 << 13)
+#define TSI108_STAT_CARRY1_RXALIGN (1 << 8)
+#define TSI108_STAT_CARRY1_RXLENGTH (1 << 7)
+#define TSI108_STAT_CARRY1_RXRUNT (1 << 4)
+#define TSI108_STAT_CARRY1_RXJUMBO (1 << 3)
+#define TSI108_STAT_CARRY1_RXFRAG (1 << 2)
+#define TSI108_STAT_CARRY1_RXJABBER (1 << 1)
+#define TSI108_STAT_CARRY1_RXDROP (1 << 0)
+
+#define TSI108_STAT_CARRY2 (0x104)
+#define TSI108_STAT_CARRY2_TXBYTES (1 << 13)
+#define TSI108_STAT_CARRY2_TXPKTS (1 << 12)
+#define TSI108_STAT_CARRY2_TXEXDEF (1 << 7)
+#define TSI108_STAT_CARRY2_TXEXCOL (1 << 3)
+#define TSI108_STAT_CARRY2_TXTCOL (1 << 2)
+#define TSI108_STAT_CARRY2_TXPAUSE (1 << 0)
+
+#define TSI108_STAT_CARRYMASK1 (0x108)
+#define TSI108_STAT_CARRYMASK2 (0x10c)
+
+#define TSI108_EC_PORTCTRL (0x200)
+#define TSI108_EC_PORTCTRL_STATRST (1 << 31)
+#define TSI108_EC_PORTCTRL_STATEN (1 << 28)
+#define TSI108_EC_PORTCTRL_NOGIG (1 << 18)
+#define TSI108_EC_PORTCTRL_HALFDUPLEX (1 << 16)
+
+#define TSI108_EC_INTSTAT (0x204)
+#define TSI108_EC_INTMASK (0x208)
+
+#define TSI108_INT_ANY (1 << 31)
+#define TSI108_INT_SFN (1 << 30)
+#define TSI108_INT_RXIDLE (1 << 29)
+#define TSI108_INT_RXABORT (1 << 28)
+#define TSI108_INT_RXERROR (1 << 27)
+#define TSI108_INT_RXOVERRUN (1 << 26)
+#define TSI108_INT_RXTHRESH (1 << 25)
+#define TSI108_INT_RXWAIT (1 << 24)
+#define TSI108_INT_RXQUEUE0 (1 << 16)
+#define TSI108_INT_STATCARRY (1 << 15)
+#define TSI108_INT_TXIDLE (1 << 13)
+#define TSI108_INT_TXABORT (1 << 12)
+#define TSI108_INT_TXERROR (1 << 11)
+#define TSI108_INT_TXUNDERRUN (1 << 10)
+#define TSI108_INT_TXTHRESH (1 << 9)
+#define TSI108_INT_TXWAIT (1 << 8)
+#define TSI108_INT_TXQUEUE0 (1 << 0)
+
+#define TSI108_EC_TXCFG (0x220)
+#define TSI108_EC_TXCFG_RST (1 << 31)
+
+#define TSI108_EC_TXCTRL (0x224)
+#define TSI108_EC_TXCTRL_IDLEINT (1 << 31)
+#define TSI108_EC_TXCTRL_ABORT (1 << 30)
+#define TSI108_EC_TXCTRL_GO (1 << 15)
+#define TSI108_EC_TXCTRL_QUEUE0 (1 << 0)
+
+#define TSI108_EC_TXSTAT (0x228)
+#define TSI108_EC_TXSTAT_ACTIVE (1 << 15)
+#define TSI108_EC_TXSTAT_QUEUE0 (1 << 0)
+
+#define TSI108_EC_TXESTAT (0x22c)
+#define TSI108_EC_TXESTAT_Q0_ERR (1 << 24)
+#define TSI108_EC_TXESTAT_Q0_DESCINT (1 << 16)
+#define TSI108_EC_TXESTAT_Q0_EOF (1 << 8)
+#define TSI108_EC_TXESTAT_Q0_EOQ (1 << 0)
+
+#define TSI108_EC_TXERR (0x278)
+
+#define TSI108_EC_TXQ_CFG (0x280)
+#define TSI108_EC_TXQ_CFG_DESC_INT (1 << 20)
+#define TSI108_EC_TXQ_CFG_EOQ_OWN_INT (1 << 19)
+#define TSI108_EC_TXQ_CFG_WSWP (1 << 11)
+#define TSI108_EC_TXQ_CFG_BSWP (1 << 10)
+#define TSI108_EC_TXQ_CFG_SFNPORT 0
+
+#define TSI108_EC_TXQ_BUFCFG (0x284)
+#define TSI108_EC_TXQ_BUFCFG_BURST8 (0 << 8)
+#define TSI108_EC_TXQ_BUFCFG_BURST32 (1 << 8)
+#define TSI108_EC_TXQ_BUFCFG_BURST128 (2 << 8)
+#define TSI108_EC_TXQ_BUFCFG_BURST256 (3 << 8)
+#define TSI108_EC_TXQ_BUFCFG_WSWP (1 << 11)
+#define TSI108_EC_TXQ_BUFCFG_BSWP (1 << 10)
+#define TSI108_EC_TXQ_BUFCFG_SFNPORT 0
+
+#define TSI108_EC_TXQ_PTRLOW (0x288)
+
+#define TSI108_EC_TXQ_PTRHIGH (0x28c)
+#define TSI108_EC_TXQ_PTRHIGH_VALID (1 << 31)
+
+#define TSI108_EC_TXTHRESH (0x230)
+#define TSI108_EC_TXTHRESH_STARTFILL 0
+#define TSI108_EC_TXTHRESH_STOPFILL 16
+
+#define TSI108_EC_RXCFG (0x320)
+#define TSI108_EC_RXCFG_RST (1 << 31)
+
+#define TSI108_EC_RXSTAT (0x328)
+#define TSI108_EC_RXSTAT_ACTIVE (1 << 15)
+#define TSI108_EC_RXSTAT_QUEUE0 (1 << 0)
+
+#define TSI108_EC_RXESTAT (0x32c)
+#define TSI108_EC_RXESTAT_Q0_ERR (1 << 24)
+#define TSI108_EC_RXESTAT_Q0_DESCINT (1 << 16)
+#define TSI108_EC_RXESTAT_Q0_EOF (1 << 8)
+#define TSI108_EC_RXESTAT_Q0_EOQ (1 << 0)
+
+#define TSI108_EC_HASHADDR (0x360)
+#define TSI108_EC_HASHADDR_AUTOINC (1 << 31)
+#define TSI108_EC_HASHADDR_DO1STREAD (1 << 30)
+#define TSI108_EC_HASHADDR_UNICAST (0 << 4)
+#define TSI108_EC_HASHADDR_MCAST (1 << 4)
+
+#define TSI108_EC_HASHDATA (0x364)
+
+#define TSI108_EC_RXQ_PTRLOW (0x388)
+
+#define TSI108_EC_RXQ_PTRHIGH (0x38c)
+#define TSI108_EC_RXQ_PTRHIGH_VALID (1 << 31)
+
+/* Station Enable -- accept packets destined for us */
+#define TSI108_EC_RXCFG_SE (1 << 13)
+/* Unicast Frame Enable -- for packets not destined for us */
+#define TSI108_EC_RXCFG_UFE (1 << 12)
+/* Multicast Frame Enable */
+#define TSI108_EC_RXCFG_MFE (1 << 11)
+/* Broadcast Frame Enable */
+#define TSI108_EC_RXCFG_BFE (1 << 10)
+#define TSI108_EC_RXCFG_UC_HASH (1 << 9)
+#define TSI108_EC_RXCFG_MC_HASH (1 << 8)
+
+#define TSI108_EC_RXQ_CFG (0x380)
+#define TSI108_EC_RXQ_CFG_DESC_INT (1 << 20)
+#define TSI108_EC_RXQ_CFG_EOQ_OWN_INT (1 << 19)
+#define TSI108_EC_RXQ_CFG_WSWP (1 << 11)
+#define TSI108_EC_RXQ_CFG_BSWP (1 << 10)
+#define TSI108_EC_RXQ_CFG_SFNPORT 0
+
+#define TSI108_EC_RXQ_BUFCFG (0x384)
+#define TSI108_EC_RXQ_BUFCFG_BURST8 (0 << 8)
+#define TSI108_EC_RXQ_BUFCFG_BURST32 (1 << 8)
+#define TSI108_EC_RXQ_BUFCFG_BURST128 (2 << 8)
+#define TSI108_EC_RXQ_BUFCFG_BURST256 (3 << 8)
+#define TSI108_EC_RXQ_BUFCFG_WSWP (1 << 11)
+#define TSI108_EC_RXQ_BUFCFG_BSWP (1 << 10)
+#define TSI108_EC_RXQ_BUFCFG_SFNPORT 0
+
+#define TSI108_EC_RXCTRL (0x324)
+#define TSI108_EC_RXCTRL_ABORT (1 << 30)
+#define TSI108_EC_RXCTRL_GO (1 << 15)
+#define TSI108_EC_RXCTRL_QUEUE0 (1 << 0)
+
+#define TSI108_EC_RXERR (0x378)
+
+#define PHY_CTRL 0
+#define PHY_CTRL_RESET (1 << 15)
+#define PHY_CTRL_AUTONEG_EN (1 << 12)
+#define PHY_CTRL_POWERDOWN (1 << 11)
+#define PHY_CTRL_AUTONEG_START (1 << 9)
+
+#define PHY_STAT 1
+#define PHY_STAT_LINKUP (1 << 2)
+
+#if (TSI108_PHY_TYPE == PHY_MV88E)
+/* Marvel 88E1xxx-specific */
+#define PHY_SUM_STAT 0x11
+#define PHY_SUM_STAT_SPEED_MASK (3 << 14)
+#define PHY_SUM_STAT_SPEED_10 (0 << 14)
+#define PHY_SUM_STAT_SPEED_100 (1 << 14)
+#define PHY_SUM_STAT_SPEED_1000 (2 << 14)
+#define PHY_SUM_STAT_FULLDUPLEX (1 << 13)
+
+#define PHY_SUM_STAT_1000T_FD (PHY_SUM_STAT_SPEED_1000 | PHY_SUM_STAT_FULLDUPLEX)
+#define PHY_SUM_STAT_1000T_HD (PHY_SUM_STAT_SPEED_1000)
+#define PHY_SUM_STAT_100TX_FD (PHY_SUM_STAT_SPEED_100 | PHY_SUM_STAT_FULLDUPLEX)
+#define PHY_SUM_STAT_100TX_HD (PHY_SUM_STAT_SPEED_100)
+#define PHY_SUM_STAT_10T_FD (PHY_SUM_STAT_SPEED_10 | PHY_SUM_STAT_FULLDUPLEX)
+#define PHY_SUM_STAT_10T_HD (PHY_SUM_STAT_SPEED_10)
+#elif (TSI108_PHY_TYPE == PHY_BCM54XX)
+/*Broadcom BCM54xx */
+#define PHY_SUM_STAT 0x19
+#define PHY_SUM_STAT_SPEED_MASK (7 << 8) /* Auto Negotiation HCD */
+#define PHY_SUM_STAT_1000T_FD (7 << 8) /* 1000BASE-T Full-Duplex */
+#define PHY_SUM_STAT_1000T_HD (6 << 8) /* 1000BASE-T Half-Duplex */
+#define PHY_SUM_STAT_100TX_FD (5 << 8) /* 100BASE-TX Full-Duplex */
+#define PHY_SUM_STAT_100T4 (4 << 8) /* 100BASE-T4 */
+#define PHY_SUM_STAT_100TX_HD (3 << 8) /* 100BASE-TX Half-Duplex */
+#define PHY_SUM_STAT_10T_FD (2 << 8) /* 10BASE-T Full-Duplex */
+#define PHY_SUM_STAT_10T_HD (1 << 8) /* 10BASE-T Half-Duplex */
+#define PHY_SUM_STAT_AN_FAIL (0 << 8) /* 1000BASE-T Half-Duplex */
+#else
+#error "PHY Device not specified"
+#endif /* MV88_PHY */
+
+#define TSI108_TX_EOF (1 << 0) /* End of frame; last fragment of packet */
+#define TSI108_TX_SOF (1 << 1) /* Start of frame; first frag. of packet */
+#define TSI108_TX_VLAN (1 << 2) /* Per-frame VLAN: enables VLAN override */
+#define TSI108_TX_HUGE (1 << 3) /* Huge frame enable */
+#define TSI108_TX_PAD (1 << 4) /* Pad the packet if too short */
+#define TSI108_TX_CRC (1 << 5) /* Generate CRC for this packet */
+#define TSI108_TX_INT (1 << 14) /* Generate an IRQ after frag. processed */
+#define TSI108_TX_RETRY 16 /* 4 bit field indicating num. of retries */
+#define TSI108_TX_COL (1 << 20) /* Set if a collision occured */
+#define TSI108_TX_LCOL (1 << 24) /* Set if a late collision occured */
+#define TSI108_TX_UNDER (1 << 25) /* Set if a FIFO underrun occured */
+#define TSI108_TX_RLIM (1 << 26) /* Set if the retry limit was reached */
+#define TSI108_TX_OK (1 << 30) /* Set if the frame TX was successful */
+#define TSI108_TX_OWN (1 << 31) /* Set if the device owns the descriptor */
+
+/* Note: the descriptor layouts assume big-endian byte order. */
+typedef struct {
+ u32 buf0;
+ u32 buf1; /* Base address of buffer */
+ u32 next0; /* Address of next descriptor, if any */
+ u32 next1;
+ u16 vlan; /* VLAN, if override enabled for this packet */
+ u16 len; /* Length of buffer in bytes */
+ u32 misc; /* See TSI108_TX_* above */
+ u32 reserved0; /*reserved0 and reserved1 are added to make the desc */
+ u32 reserved1; /* 32-byte aligned */
+} __attribute__ ((aligned(32))) tx_desc;
+
+#define TSI108_RX_EOF (1 << 0) /* End of frame; last fragment of packet */
+#define TSI108_RX_SOF (1 << 1) /* Start of frame; first frag. of packet */
+#define TSI108_RX_VLAN (1 << 2) /* Set on SOF if packet has a VLAN */
+#define TSI108_RX_FTYPE (1 << 3) /* Length/Type field is type, not length */
+#define TSI108_RX_RUNT (1 << 4)/* Packet is less than minimum size */
+#define TSI108_RX_HASH (1 << 7)/* Hash table match */
+#define TSI108_RX_BAD (1 << 8) /* Bad frame */
+#define TSI108_RX_OVER (1 << 9) /* FIFO overrun occured */
+#define TSI108_RX_TRUNC (1 << 11) /* Packet truncated due to excess length */
+#define TSI108_RX_CRC (1 << 12) /* Packet had a CRC error */
+#define TSI108_RX_INT (1 << 13) /* Generate an IRQ after frag. processed */
+#define TSI108_RX_OWN (1 << 15) /* Set if the device owns the descriptor */
+
+typedef struct {
+ u32 buf0; /* Base address of buffer */
+ u32 buf1; /* Base address of buffer */
+ u32 next0; /* Address of next descriptor, if any */
+ u32 next1; /* Address of next descriptor, if any */
+ u16 vlan; /* VLAN of received packet, first frag only */
+ u16 len; /* Length of received fragment in bytes */
+ u16 blen; /* Length of buffer in bytes */
+ u16 misc; /* See TSI108_RX_* above */
+ u32 reserved0; /* reserved0 and reserved1 are added to make the desc */
+ u32 reserved1; /* 32-byte aligned */
+} __attribute__ ((aligned(32))) rx_desc;
+
+#endif /* __TSI108_ETH_H */
--
1.3.0
^ permalink raw reply related
* [PATCH/2.6.17-rc4 7/10] Powerpc: workaround for tsi108 pci confi g read exception
From: Zang Roy-r61911 @ 2006-05-17 10:14 UTC (permalink / raw)
To: Paul Mackerras; +Cc: linuxppc-dev list, Alexandre.Bounine, Yang Xin-Xin-r48390
Workaround for Tundra Semiconductor tsi108 host bridge pci config read
exception
Signed-off-by: Alexandre Bounine <alexandreb@tundra.com>
Signed-off-by: Roy Zang <tie-fei.zang@freescale.com>
---
arch/powerpc/kernel/traps.c | 13 +++++++++++++
1 files changed, 13 insertions(+), 0 deletions(-)
0575fbe21e4f1045528bb91ec4b34bb7955c4a92
diff --git a/arch/powerpc/kernel/traps.c b/arch/powerpc/kernel/traps.c
index 064a525..7468d76 100644
--- a/arch/powerpc/kernel/traps.c
+++ b/arch/powerpc/kernel/traps.c
@@ -262,6 +262,19 @@ #if defined(CONFIG_PPC_PMAC) && defined(
}
}
#endif /* CONFIG_PPC_PMAC && CONFIG_PPC32 */
+
+#ifdef CONFIG_TSI108_BRIDGE
+ extern void tsi108_clear_pci_cfg_error(void);
+ const struct exception_table_entry *entry;
+
+ /* Are we prepared to handle this fault? */
+ if ((entry = search_exception_tables(regs->nip)) != NULL) {
+ tsi108_clear_pci_cfg_error();
+ regs->msr |= MSR_RI;
+ regs->nip = entry->fixup;
+ return 1;
+ }
+#endif /* CONFIG_TSI108_BRIDGE */
return 0;
}
--
1.3.0
^ 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