diff options
Diffstat (limited to 'arch/sparc64/kernel/chmc.c')
-rw-r--r-- | arch/sparc64/kernel/chmc.c | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/arch/sparc64/kernel/chmc.c b/arch/sparc64/kernel/chmc.c new file mode 100644 index 000000000000..97cf912f0853 --- /dev/null +++ b/arch/sparc64/kernel/chmc.c @@ -0,0 +1,458 @@ +/* $Id: chmc.c,v 1.4 2002/01/08 16:00:14 davem Exp $ + * memctrlr.c: Driver for UltraSPARC-III memory controller. + * + * Copyright (C) 2001 David S. Miller (davem@redhat.com) + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/list.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/smp.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <asm/spitfire.h> +#include <asm/chmctrl.h> +#include <asm/oplib.h> +#include <asm/io.h> + +#define CHMCTRL_NDGRPS 2 +#define CHMCTRL_NDIMMS 4 + +#define DIMMS_PER_MC (CHMCTRL_NDGRPS * CHMCTRL_NDIMMS) + +/* OBP memory-layout property format. */ +struct obp_map { + unsigned char dimm_map[144]; + unsigned char pin_map[576]; +}; + +#define DIMM_LABEL_SZ 8 + +struct obp_mem_layout { + /* One max 8-byte string label per DIMM. Usually + * this matches the label on the motherboard where + * that DIMM resides. + */ + char dimm_labels[DIMMS_PER_MC][DIMM_LABEL_SZ]; + + /* If symmetric use map[0], else it is + * asymmetric and map[1] should be used. + */ + char symmetric; + + struct obp_map map[2]; +}; + +#define CHMCTRL_NBANKS 4 + +struct bank_info { + struct mctrl_info *mp; + int bank_id; + + u64 raw_reg; + int valid; + int uk; + int um; + int lk; + int lm; + int interleave; + unsigned long base; + unsigned long size; +}; + +struct mctrl_info { + struct list_head list; + int portid; + int index; + + struct obp_mem_layout layout_prop; + int layout_size; + + void __iomem *regs; + + u64 timing_control1; + u64 timing_control2; + u64 timing_control3; + u64 timing_control4; + u64 memaddr_control; + + struct bank_info logical_banks[CHMCTRL_NBANKS]; +}; + +static LIST_HEAD(mctrl_list); + +/* Does BANK decode PHYS_ADDR? */ +static int bank_match(struct bank_info *bp, unsigned long phys_addr) +{ + unsigned long upper_bits = (phys_addr & PA_UPPER_BITS) >> PA_UPPER_BITS_SHIFT; + unsigned long lower_bits = (phys_addr & PA_LOWER_BITS) >> PA_LOWER_BITS_SHIFT; + + /* Bank must be enabled to match. */ + if (bp->valid == 0) + return 0; + + /* Would BANK match upper bits? */ + upper_bits ^= bp->um; /* What bits are different? */ + upper_bits = ~upper_bits; /* Invert. */ + upper_bits |= bp->uk; /* What bits don't matter for matching? */ + upper_bits = ~upper_bits; /* Invert. */ + + if (upper_bits) + return 0; + + /* Would BANK match lower bits? */ + lower_bits ^= bp->lm; /* What bits are different? */ + lower_bits = ~lower_bits; /* Invert. */ + lower_bits |= bp->lk; /* What bits don't matter for matching? */ + lower_bits = ~lower_bits; /* Invert. */ + + if (lower_bits) + return 0; + + /* I always knew you'd be the one. */ + return 1; +} + +/* Given PHYS_ADDR, search memory controller banks for a match. */ +static struct bank_info *find_bank(unsigned long phys_addr) +{ + struct list_head *mctrl_head = &mctrl_list; + struct list_head *mctrl_entry = mctrl_head->next; + + for (;;) { + struct mctrl_info *mp = + list_entry(mctrl_entry, struct mctrl_info, list); + int bank_no; + + if (mctrl_entry == mctrl_head) + break; + mctrl_entry = mctrl_entry->next; + + for (bank_no = 0; bank_no < CHMCTRL_NBANKS; bank_no++) { + struct bank_info *bp; + + bp = &mp->logical_banks[bank_no]; + if (bank_match(bp, phys_addr)) + return bp; + } + } + + return NULL; +} + +/* This is the main purpose of this driver. */ +#define SYNDROME_MIN -1 +#define SYNDROME_MAX 144 +int chmc_getunumber(int syndrome_code, + unsigned long phys_addr, + char *buf, int buflen) +{ + struct bank_info *bp; + struct obp_mem_layout *prop; + int bank_in_controller, first_dimm; + + bp = find_bank(phys_addr); + if (bp == NULL || + syndrome_code < SYNDROME_MIN || + syndrome_code > SYNDROME_MAX) { + buf[0] = '?'; + buf[1] = '?'; + buf[2] = '?'; + buf[3] = '\0'; + return 0; + } + + prop = &bp->mp->layout_prop; + bank_in_controller = bp->bank_id & (CHMCTRL_NBANKS - 1); + first_dimm = (bank_in_controller & (CHMCTRL_NDGRPS - 1)); + first_dimm *= CHMCTRL_NDIMMS; + + if (syndrome_code != SYNDROME_MIN) { + struct obp_map *map; + int qword, where_in_line, where, map_index, map_offset; + unsigned int map_val; + + /* Yaay, single bit error so we can figure out + * the exact dimm. + */ + if (prop->symmetric) + map = &prop->map[0]; + else + map = &prop->map[1]; + + /* Covert syndrome code into the way the bits are + * positioned on the bus. + */ + if (syndrome_code < 144 - 16) + syndrome_code += 16; + else if (syndrome_code < 144) + syndrome_code -= (144 - 7); + else if (syndrome_code < (144 + 3)) + syndrome_code -= (144 + 3 - 4); + else + syndrome_code -= 144 + 3; + + /* All this magic has to do with how a cache line + * comes over the wire on Safari. A 64-bit line + * comes over in 4 quadword cycles, each of which + * transmit ECC/MTAG info as well as the actual + * data. 144 bits per quadword, 576 total. + */ +#define LINE_SIZE 64 +#define LINE_ADDR_MSK (LINE_SIZE - 1) +#define QW_PER_LINE 4 +#define QW_BYTES (LINE_SIZE / QW_PER_LINE) +#define QW_BITS 144 +#define LAST_BIT (576 - 1) + + qword = (phys_addr & LINE_ADDR_MSK) / QW_BYTES; + where_in_line = ((3 - qword) * QW_BITS) + syndrome_code; + where = (LAST_BIT - where_in_line); + map_index = where >> 2; + map_offset = where & 0x3; + map_val = map->dimm_map[map_index]; + map_val = ((map_val >> ((3 - map_offset) << 1)) & (2 - 1)); + + sprintf(buf, "%s, pin %3d", + prop->dimm_labels[first_dimm + map_val], + map->pin_map[where_in_line]); + } else { + int dimm; + + /* Multi-bit error, we just dump out all the + * dimm labels associated with this bank. + */ + for (dimm = 0; dimm < CHMCTRL_NDIMMS; dimm++) { + sprintf(buf, "%s ", + prop->dimm_labels[first_dimm + dimm]); + buf += strlen(buf); + } + } + return 0; +} + +/* Accessing the registers is slightly complicated. If you want + * to get at the memory controller which is on the same processor + * the code is executing, you must use special ASI load/store else + * you go through the global mapping. + */ +static u64 read_mcreg(struct mctrl_info *mp, unsigned long offset) +{ + unsigned long ret; + int this_cpu = get_cpu(); + + if (mp->portid == this_cpu) { + __asm__ __volatile__("ldxa [%1] %2, %0" + : "=r" (ret) + : "r" (offset), "i" (ASI_MCU_CTRL_REG)); + } else { + __asm__ __volatile__("ldxa [%1] %2, %0" + : "=r" (ret) + : "r" (mp->regs + offset), + "i" (ASI_PHYS_BYPASS_EC_E)); + } + put_cpu(); + + return ret; +} + +#if 0 /* currently unused */ +static void write_mcreg(struct mctrl_info *mp, unsigned long offset, u64 val) +{ + if (mp->portid == smp_processor_id()) { + __asm__ __volatile__("stxa %0, [%1] %2" + : : "r" (val), + "r" (offset), "i" (ASI_MCU_CTRL_REG)); + } else { + __asm__ __volatile__("ldxa %0, [%1] %2" + : : "r" (val), + "r" (mp->regs + offset), + "i" (ASI_PHYS_BYPASS_EC_E)); + } +} +#endif + +static void interpret_one_decode_reg(struct mctrl_info *mp, int which_bank, u64 val) +{ + struct bank_info *p = &mp->logical_banks[which_bank]; + + p->mp = mp; + p->bank_id = (CHMCTRL_NBANKS * mp->portid) + which_bank; + p->raw_reg = val; + p->valid = (val & MEM_DECODE_VALID) >> MEM_DECODE_VALID_SHIFT; + p->uk = (val & MEM_DECODE_UK) >> MEM_DECODE_UK_SHIFT; + p->um = (val & MEM_DECODE_UM) >> MEM_DECODE_UM_SHIFT; + p->lk = (val & MEM_DECODE_LK) >> MEM_DECODE_LK_SHIFT; + p->lm = (val & MEM_DECODE_LM) >> MEM_DECODE_LM_SHIFT; + + p->base = (p->um); + p->base &= ~(p->uk); + p->base <<= PA_UPPER_BITS_SHIFT; + + switch(p->lk) { + case 0xf: + default: + p->interleave = 1; + break; + + case 0xe: + p->interleave = 2; + break; + + case 0xc: + p->interleave = 4; + break; + + case 0x8: + p->interleave = 8; + break; + + case 0x0: + p->interleave = 16; + break; + }; + + /* UK[10] is reserved, and UK[11] is not set for the SDRAM + * bank size definition. + */ + p->size = (((unsigned long)p->uk & + ((1UL << 10UL) - 1UL)) + 1UL) << PA_UPPER_BITS_SHIFT; + p->size /= p->interleave; +} + +static void fetch_decode_regs(struct mctrl_info *mp) +{ + if (mp->layout_size == 0) + return; + + interpret_one_decode_reg(mp, 0, + read_mcreg(mp, CHMCTRL_DECODE1)); + interpret_one_decode_reg(mp, 1, + read_mcreg(mp, CHMCTRL_DECODE2)); + interpret_one_decode_reg(mp, 2, + read_mcreg(mp, CHMCTRL_DECODE3)); + interpret_one_decode_reg(mp, 3, + read_mcreg(mp, CHMCTRL_DECODE4)); +} + +static int init_one_mctrl(int node, int index) +{ + struct mctrl_info *mp = kmalloc(sizeof(*mp), GFP_KERNEL); + int portid = prom_getintdefault(node, "portid", -1); + struct linux_prom64_registers p_reg_prop; + int t; + + if (!mp) + return -1; + memset(mp, 0, sizeof(*mp)); + if (portid == -1) + goto fail; + + mp->portid = portid; + mp->layout_size = prom_getproplen(node, "memory-layout"); + if (mp->layout_size < 0) + mp->layout_size = 0; + if (mp->layout_size > sizeof(mp->layout_prop)) + goto fail; + + if (mp->layout_size > 0) + prom_getproperty(node, "memory-layout", + (char *) &mp->layout_prop, + mp->layout_size); + + t = prom_getproperty(node, "reg", + (char *) &p_reg_prop, + sizeof(p_reg_prop)); + if (t < 0 || p_reg_prop.reg_size != 0x48) + goto fail; + + mp->regs = ioremap(p_reg_prop.phys_addr, p_reg_prop.reg_size); + if (mp->regs == NULL) + goto fail; + + if (mp->layout_size != 0UL) { + mp->timing_control1 = read_mcreg(mp, CHMCTRL_TCTRL1); + mp->timing_control2 = read_mcreg(mp, CHMCTRL_TCTRL2); + mp->timing_control3 = read_mcreg(mp, CHMCTRL_TCTRL3); + mp->timing_control4 = read_mcreg(mp, CHMCTRL_TCTRL4); + mp->memaddr_control = read_mcreg(mp, CHMCTRL_MACTRL); + } + + fetch_decode_regs(mp); + + mp->index = index; + + list_add(&mp->list, &mctrl_list); + + /* Report the device. */ + printk(KERN_INFO "chmc%d: US3 memory controller at %p [%s]\n", + mp->index, + mp->regs, (mp->layout_size ? "ACTIVE" : "INACTIVE")); + + return 0; + +fail: + if (mp) { + if (mp->regs != NULL) + iounmap(mp->regs); + kfree(mp); + } + return -1; +} + +static int __init probe_for_string(char *name, int index) +{ + int node = prom_getchild(prom_root_node); + + while ((node = prom_searchsiblings(node, name)) != 0) { + int ret = init_one_mctrl(node, index); + + if (!ret) + index++; + + node = prom_getsibling(node); + if (!node) + break; + } + + return index; +} + +static int __init chmc_init(void) +{ + int index; + + /* This driver is only for cheetah platforms. */ + if (tlb_type != cheetah && tlb_type != cheetah_plus) + return -ENODEV; + + index = probe_for_string("memory-controller", 0); + index = probe_for_string("mc-us3", index); + + return 0; +} + +static void __exit chmc_cleanup(void) +{ + struct list_head *head = &mctrl_list; + struct list_head *tmp = head->next; + + for (;;) { + struct mctrl_info *p = + list_entry(tmp, struct mctrl_info, list); + if (tmp == head) + break; + tmp = tmp->next; + + list_del(&p->list); + iounmap(p->regs); + kfree(p); + } +} + +module_init(chmc_init); +module_exit(chmc_cleanup); |