diff options
Diffstat (limited to 'drivers/edac')
-rw-r--r-- | drivers/edac/Kconfig | 36 | ||||
-rw-r--r-- | drivers/edac/Makefile | 2 | ||||
-rw-r--r-- | drivers/edac/amd8111_edac.c | 595 | ||||
-rw-r--r-- | drivers/edac/amd8111_edac.h | 130 | ||||
-rw-r--r-- | drivers/edac/amd8131_edac.c | 379 | ||||
-rw-r--r-- | drivers/edac/amd8131_edac.h | 119 | ||||
-rw-r--r-- | drivers/edac/cell_edac.c | 2 | ||||
-rw-r--r-- | drivers/edac/edac_core.h | 16 | ||||
-rw-r--r-- | drivers/edac/edac_pci.c | 14 | ||||
-rw-r--r-- | drivers/edac/mpc85xx_edac.c | 2 | ||||
-rw-r--r-- | drivers/edac/mv64x60_edac.c | 8 | ||||
-rw-r--r-- | drivers/edac/ppc4xx_edac.c | 1448 | ||||
-rw-r--r-- | drivers/edac/ppc4xx_edac.h | 172 |
13 files changed, 2912 insertions, 11 deletions
diff --git a/drivers/edac/Kconfig b/drivers/edac/Kconfig index eee47fd16d79..e5f5c5a8ba6c 100644 --- a/drivers/edac/Kconfig +++ b/drivers/edac/Kconfig @@ -1,13 +1,12 @@ # # EDAC Kconfig -# Copyright (c) 2003 Linux Networx +# Copyright (c) 2008 Doug Thompson www.softwarebitmaker.com # Licensed and distributed under the GPL # menuconfig EDAC - bool "EDAC - error detection and reporting (EXPERIMENTAL)" + bool "EDAC - error detection and reporting" depends on HAS_IOMEM - depends on EXPERIMENTAL depends on X86 || PPC help EDAC is designed to report errors in the core system. @@ -40,6 +39,14 @@ config EDAC_DEBUG there're four debug levels (x=0,1,2,3 from low to high). Usually you should select 'N'. +config EDAC_DEBUG_VERBOSE + bool "More verbose debugging" + depends on EDAC_DEBUG + help + This option makes debugging information more verbose. + Source file name and line number where debugging message + printed will be added to debugging message. + config EDAC_MM_EDAC tristate "Main Memory EDAC (Error Detection And Correction) reporting" default y @@ -174,4 +181,27 @@ config EDAC_CELL Cell Broadband Engine internal memory controller on platform without a hypervisor +config EDAC_PPC4XX + tristate "PPC4xx IBM DDR2 Memory Controller" + depends on EDAC_MM_EDAC && 4xx + help + This enables support for EDAC on the ECC memory used + with the IBM DDR2 memory controller found in various + PowerPC 4xx embedded processors such as the 405EX[r], + 440SP, 440SPe, 460EX, 460GT and 460SX. + +config EDAC_AMD8131 + tristate "AMD8131 HyperTransport PCI-X Tunnel" + depends on EDAC_MM_EDAC && PCI + help + Support for error detection and correction on the + AMD8131 HyperTransport PCI-X Tunnel chip. + +config EDAC_AMD8111 + tristate "AMD8111 HyperTransport I/O Hub" + depends on EDAC_MM_EDAC && PCI + help + Support for error detection and correction on the + AMD8111 HyperTransport I/O Hub chip. + endif # EDAC diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile index b75196927de3..a5fdcf02f591 100644 --- a/drivers/edac/Makefile +++ b/drivers/edac/Makefile @@ -34,4 +34,4 @@ obj-$(CONFIG_EDAC_PASEMI) += pasemi_edac.o obj-$(CONFIG_EDAC_MPC85XX) += mpc85xx_edac.o obj-$(CONFIG_EDAC_MV64X60) += mv64x60_edac.o obj-$(CONFIG_EDAC_CELL) += cell_edac.o - +obj-$(CONFIG_EDAC_PPC4XX) += ppc4xx_edac.o diff --git a/drivers/edac/amd8111_edac.c b/drivers/edac/amd8111_edac.c new file mode 100644 index 000000000000..614692181120 --- /dev/null +++ b/drivers/edac/amd8111_edac.c @@ -0,0 +1,595 @@ +/* + * amd8111_edac.c, AMD8111 Hyper Transport chip EDAC kernel module + * + * Copyright (c) 2008 Wind River Systems, Inc. + * + * Authors: Cao Qingtao <qingtao.cao@windriver.com> + * Benjamin Walsh <benjamin.walsh@windriver.com> + * Hu Yongqi <yongqi.hu@windriver.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/bitops.h> +#include <linux/edac.h> +#include <linux/pci_ids.h> +#include <asm/io.h> + +#include "edac_core.h" +#include "edac_module.h" +#include "amd8111_edac.h" + +#define AMD8111_EDAC_REVISION " Ver: 1.0.0 " __DATE__ +#define AMD8111_EDAC_MOD_STR "amd8111_edac" + +#define PCI_DEVICE_ID_AMD_8111_PCI 0x7460 +static int edac_dev_idx; + +enum amd8111_edac_devs { + LPC_BRIDGE = 0, +}; + +enum amd8111_edac_pcis { + PCI_BRIDGE = 0, +}; + +/* Wrapper functions for accessing PCI configuration space */ +static int edac_pci_read_dword(struct pci_dev *dev, int reg, u32 *val32) +{ + int ret; + + ret = pci_read_config_dword(dev, reg, val32); + if (ret != 0) + printk(KERN_ERR AMD8111_EDAC_MOD_STR + " PCI Access Read Error at 0x%x\n", reg); + + return ret; +} + +static void edac_pci_read_byte(struct pci_dev *dev, int reg, u8 *val8) +{ + int ret; + + ret = pci_read_config_byte(dev, reg, val8); + if (ret != 0) + printk(KERN_ERR AMD8111_EDAC_MOD_STR + " PCI Access Read Error at 0x%x\n", reg); +} + +static void edac_pci_write_dword(struct pci_dev *dev, int reg, u32 val32) +{ + int ret; + + ret = pci_write_config_dword(dev, reg, val32); + if (ret != 0) + printk(KERN_ERR AMD8111_EDAC_MOD_STR + " PCI Access Write Error at 0x%x\n", reg); +} + +static void edac_pci_write_byte(struct pci_dev *dev, int reg, u8 val8) +{ + int ret; + + ret = pci_write_config_byte(dev, reg, val8); + if (ret != 0) + printk(KERN_ERR AMD8111_EDAC_MOD_STR + " PCI Access Write Error at 0x%x\n", reg); +} + +/* + * device-specific methods for amd8111 PCI Bridge Controller + * + * Error Reporting and Handling for amd8111 chipset could be found + * in its datasheet 3.1.2 section, P37 + */ +static void amd8111_pci_bridge_init(struct amd8111_pci_info *pci_info) +{ + u32 val32; + struct pci_dev *dev = pci_info->dev; + + /* First clear error detection flags on the host interface */ + + /* Clear SSE/SMA/STA flags in the global status register*/ + edac_pci_read_dword(dev, REG_PCI_STSCMD, &val32); + if (val32 & PCI_STSCMD_CLEAR_MASK) + edac_pci_write_dword(dev, REG_PCI_STSCMD, val32); + + /* Clear CRC and Link Fail flags in HT Link Control reg */ + edac_pci_read_dword(dev, REG_HT_LINK, &val32); + if (val32 & HT_LINK_CLEAR_MASK) + edac_pci_write_dword(dev, REG_HT_LINK, val32); + + /* Second clear all fault on the secondary interface */ + + /* Clear error flags in the memory-base limit reg. */ + edac_pci_read_dword(dev, REG_MEM_LIM, &val32); + if (val32 & MEM_LIMIT_CLEAR_MASK) + edac_pci_write_dword(dev, REG_MEM_LIM, val32); + + /* Clear Discard Timer Expired flag in Interrupt/Bridge Control reg */ + edac_pci_read_dword(dev, REG_PCI_INTBRG_CTRL, &val32); + if (val32 & PCI_INTBRG_CTRL_CLEAR_MASK) + edac_pci_write_dword(dev, REG_PCI_INTBRG_CTRL, val32); + + /* Last enable error detections */ + if (edac_op_state == EDAC_OPSTATE_POLL) { + /* Enable System Error reporting in global status register */ + edac_pci_read_dword(dev, REG_PCI_STSCMD, &val32); + val32 |= PCI_STSCMD_SERREN; + edac_pci_write_dword(dev, REG_PCI_STSCMD, val32); + + /* Enable CRC Sync flood packets to HyperTransport Link */ + edac_pci_read_dword(dev, REG_HT_LINK, &val32); + val32 |= HT_LINK_CRCFEN; + edac_pci_write_dword(dev, REG_HT_LINK, val32); + + /* Enable SSE reporting etc in Interrupt control reg */ + edac_pci_read_dword(dev, REG_PCI_INTBRG_CTRL, &val32); + val32 |= PCI_INTBRG_CTRL_POLL_MASK; + edac_pci_write_dword(dev, REG_PCI_INTBRG_CTRL, val32); + } +} + +static void amd8111_pci_bridge_exit(struct amd8111_pci_info *pci_info) +{ + u32 val32; + struct pci_dev *dev = pci_info->dev; + + if (edac_op_state == EDAC_OPSTATE_POLL) { + /* Disable System Error reporting */ + edac_pci_read_dword(dev, REG_PCI_STSCMD, &val32); + val32 &= ~PCI_STSCMD_SERREN; + edac_pci_write_dword(dev, REG_PCI_STSCMD, val32); + + /* Disable CRC flood packets */ + edac_pci_read_dword(dev, REG_HT_LINK, &val32); + val32 &= ~HT_LINK_CRCFEN; + edac_pci_write_dword(dev, REG_HT_LINK, val32); + + /* Disable DTSERREN/MARSP/SERREN in Interrupt Control reg */ + edac_pci_read_dword(dev, REG_PCI_INTBRG_CTRL, &val32); + val32 &= ~PCI_INTBRG_CTRL_POLL_MASK; + edac_pci_write_dword(dev, REG_PCI_INTBRG_CTRL, val32); + } +} + +static void amd8111_pci_bridge_check(struct edac_pci_ctl_info *edac_dev) +{ + struct amd8111_pci_info *pci_info = edac_dev->pvt_info; + struct pci_dev *dev = pci_info->dev; + u32 val32; + + /* Check out PCI Bridge Status and Command Register */ + edac_pci_read_dword(dev, REG_PCI_STSCMD, &val32); + if (val32 & PCI_STSCMD_CLEAR_MASK) { + printk(KERN_INFO "Error(s) in PCI bridge status and command" + "register on device %s\n", pci_info->ctl_name); + printk(KERN_INFO "SSE: %d, RMA: %d, RTA: %d\n", + (val32 & PCI_STSCMD_SSE) != 0, + (val32 & PCI_STSCMD_RMA) != 0, + (val32 & PCI_STSCMD_RTA) != 0); + + val32 |= PCI_STSCMD_CLEAR_MASK; + edac_pci_write_dword(dev, REG_PCI_STSCMD, val32); + + edac_pci_handle_npe(edac_dev, edac_dev->ctl_name); + } + + /* Check out HyperTransport Link Control Register */ + edac_pci_read_dword(dev, REG_HT_LINK, &val32); + if (val32 & HT_LINK_LKFAIL) { + printk(KERN_INFO "Error(s) in hypertransport link control" + "register on device %s\n", pci_info->ctl_name); + printk(KERN_INFO "LKFAIL: %d\n", + (val32 & HT_LINK_LKFAIL) != 0); + + val32 |= HT_LINK_LKFAIL; + edac_pci_write_dword(dev, REG_HT_LINK, val32); + + edac_pci_handle_npe(edac_dev, edac_dev->ctl_name); + } + + /* Check out PCI Interrupt and Bridge Control Register */ + edac_pci_read_dword(dev, REG_PCI_INTBRG_CTRL, &val32); + if (val32 & PCI_INTBRG_CTRL_DTSTAT) { + printk(KERN_INFO "Error(s) in PCI interrupt and bridge control" + "register on device %s\n", pci_info->ctl_name); + printk(KERN_INFO "DTSTAT: %d\n", + (val32 & PCI_INTBRG_CTRL_DTSTAT) != 0); + + val32 |= PCI_INTBRG_CTRL_DTSTAT; + edac_pci_write_dword(dev, REG_PCI_INTBRG_CTRL, val32); + + edac_pci_handle_npe(edac_dev, edac_dev->ctl_name); + } + + /* Check out PCI Bridge Memory Base-Limit Register */ + edac_pci_read_dword(dev, REG_MEM_LIM, &val32); + if (val32 & MEM_LIMIT_CLEAR_MASK) { + printk(KERN_INFO + "Error(s) in mem limit register on %s device\n", + pci_info->ctl_name); + printk(KERN_INFO "DPE: %d, RSE: %d, RMA: %d\n" + "RTA: %d, STA: %d, MDPE: %d\n", + (val32 & MEM_LIMIT_DPE) != 0, + (val32 & MEM_LIMIT_RSE) != 0, + (val32 & MEM_LIMIT_RMA) != 0, + (val32 & MEM_LIMIT_RTA) != 0, + (val32 & MEM_LIMIT_STA) != 0, + (val32 & MEM_LIMIT_MDPE) != 0); + + val32 |= MEM_LIMIT_CLEAR_MASK; + edac_pci_write_dword(dev, REG_MEM_LIM, val32); + + edac_pci_handle_npe(edac_dev, edac_dev->ctl_name); + } +} + +static struct resource *legacy_io_res; +static int at_compat_reg_broken; +#define LEGACY_NR_PORTS 1 + +/* device-specific methods for amd8111 LPC Bridge device */ +static void amd8111_lpc_bridge_init(struct amd8111_dev_info *dev_info) +{ + u8 val8; + struct pci_dev *dev = dev_info->dev; + + /* First clear REG_AT_COMPAT[SERR, IOCHK] if necessary */ + legacy_io_res = request_region(REG_AT_COMPAT, LEGACY_NR_PORTS, + AMD8111_EDAC_MOD_STR); + if (!legacy_io_res) + printk(KERN_INFO "%s: failed to request legacy I/O region " + "start %d, len %d\n", __func__, + REG_AT_COMPAT, LEGACY_NR_PORTS); + else { + val8 = __do_inb(REG_AT_COMPAT); + if (val8 == 0xff) { /* buggy port */ + printk(KERN_INFO "%s: port %d is buggy, not supported" + " by hardware?\n", __func__, REG_AT_COMPAT); + at_compat_reg_broken = 1; + release_region(REG_AT_COMPAT, LEGACY_NR_PORTS); + legacy_io_res = NULL; + } else { + u8 out8 = 0; + if (val8 & AT_COMPAT_SERR) + out8 = AT_COMPAT_CLRSERR; + if (val8 & AT_COMPAT_IOCHK) + out8 |= AT_COMPAT_CLRIOCHK; + if (out8 > 0) + __do_outb(out8, REG_AT_COMPAT); + } + } + + /* Second clear error flags on LPC bridge */ + edac_pci_read_byte(dev, REG_IO_CTRL_1, &val8); + if (val8 & IO_CTRL_1_CLEAR_MASK) + edac_pci_write_byte(dev, REG_IO_CTRL_1, val8); +} + +static void amd8111_lpc_bridge_exit(struct amd8111_dev_info *dev_info) +{ + if (legacy_io_res) + release_region(REG_AT_COMPAT, LEGACY_NR_PORTS); +} + +static void amd8111_lpc_bridge_check(struct edac_device_ctl_info *edac_dev) +{ + struct amd8111_dev_info *dev_info = edac_dev->pvt_info; + struct pci_dev *dev = dev_info->dev; + u8 val8; + + edac_pci_read_byte(dev, REG_IO_CTRL_1, &val8); + if (val8 & IO_CTRL_1_CLEAR_MASK) { + printk(KERN_INFO + "Error(s) in IO control register on %s device\n", + dev_info->ctl_name); + printk(KERN_INFO "LPC ERR: %d, PW2LPC: %d\n", + (val8 & IO_CTRL_1_LPC_ERR) != 0, + (val8 & IO_CTRL_1_PW2LPC) != 0); + + val8 |= IO_CTRL_1_CLEAR_MASK; + edac_pci_write_byte(dev, REG_IO_CTRL_1, val8); + + edac_device_handle_ue(edac_dev, 0, 0, edac_dev->ctl_name); + } + + if (at_compat_reg_broken == 0) { + u8 out8 = 0; + val8 = __do_inb(REG_AT_COMPAT); + if (val8 & AT_COMPAT_SERR) + out8 = AT_COMPAT_CLRSERR; + if (val8 & AT_COMPAT_IOCHK) + out8 |= AT_COMPAT_CLRIOCHK; + if (out8 > 0) { + __do_outb(out8, REG_AT_COMPAT); + edac_device_handle_ue(edac_dev, 0, 0, + edac_dev->ctl_name); + } + } +} + +/* General devices represented by edac_device_ctl_info */ +static struct amd8111_dev_info amd8111_devices[] = { + [LPC_BRIDGE] = { + .err_dev = PCI_DEVICE_ID_AMD_8111_LPC, + .ctl_name = "lpc", + .init = amd8111_lpc_bridge_init, + .exit = amd8111_lpc_bridge_exit, + .check = amd8111_lpc_bridge_check, + }, + {0}, +}; + +/* PCI controllers represented by edac_pci_ctl_info */ +static struct amd8111_pci_info amd8111_pcis[] = { + [PCI_BRIDGE] = { + .err_dev = PCI_DEVICE_ID_AMD_8111_PCI, + .ctl_name = "AMD8111_PCI_Controller", + .init = amd8111_pci_bridge_init, + .exit = amd8111_pci_bridge_exit, + .check = amd8111_pci_bridge_check, + }, + {0}, +}; + +static int amd8111_dev_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct amd8111_dev_info *dev_info = &amd8111_devices[id->driver_data]; + + dev_info->dev = pci_get_device(PCI_VENDOR_ID_AMD, + dev_info->err_dev, NULL); + + if (!dev_info->dev) { + printk(KERN_ERR "EDAC device not found:" + "vendor %x, device %x, name %s\n", + PCI_VENDOR_ID_AMD, dev_info->err_dev, + dev_info->ctl_name); + return -ENODEV; + } + + if (pci_enable_device(dev_info->dev)) { + pci_dev_put(dev_info->dev); + printk(KERN_ERR "failed to enable:" + "vendor %x, device %x, name %s\n", + PCI_VENDOR_ID_AMD, dev_info->err_dev, + dev_info->ctl_name); + return -ENODEV; + } + + /* + * we do not allocate extra private structure for + * edac_device_ctl_info, but make use of existing + * one instead. + */ + dev_info->edac_idx = edac_dev_idx++; + dev_info->edac_dev = + edac_device_alloc_ctl_info(0, dev_info->ctl_name, 1, + NULL, 0, 0, + NULL, 0, dev_info->edac_idx); + if (!dev_info->edac_dev) + return -ENOMEM; + + dev_info->edac_dev->pvt_info = dev_info; + dev_info->edac_dev->dev = &dev_info->dev->dev; + dev_info->edac_dev->mod_name = AMD8111_EDAC_MOD_STR; + dev_info->edac_dev->ctl_name = dev_info->ctl_name; + dev_info->edac_dev->dev_name = dev_info->dev->dev.bus_id; + + if (edac_op_state == EDAC_OPSTATE_POLL) + dev_info->edac_dev->edac_check = dev_info->check; + + if (dev_info->init) + dev_info->init(dev_info); + + if (edac_device_add_device(dev_info->edac_dev) > 0) { + printk(KERN_ERR "failed to add edac_dev for %s\n", + dev_info->ctl_name); + edac_device_free_ctl_info(dev_info->edac_dev); + return -ENODEV; + } + + printk(KERN_INFO "added one edac_dev on AMD8111 " + "vendor %x, device %x, name %s\n", + PCI_VENDOR_ID_AMD, dev_info->err_dev, + dev_info->ctl_name); + + return 0; +} + +static void amd8111_dev_remove(struct pci_dev *dev) +{ + struct amd8111_dev_info *dev_info; + + for (dev_info = amd8111_devices; dev_info->err_dev; dev_info++) + if (dev_info->dev->device == dev->device) + break; + + if (!dev_info->err_dev) /* should never happen */ + return; + + if (dev_info->edac_dev) { + edac_device_del_device(dev_info->edac_dev->dev); + edac_device_free_ctl_info(dev_info->edac_dev); + } + + if (dev_info->exit) + dev_info->exit(dev_info); + + pci_dev_put(dev_info->dev); +} + +static int amd8111_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct amd8111_pci_info *pci_info = &amd8111_pcis[id->driver_data]; + + pci_info->dev = pci_get_device(PCI_VENDOR_ID_AMD, + pci_info->err_dev, NULL); + + if (!pci_info->dev) { + printk(KERN_ERR "EDAC device not found:" + "vendor %x, device %x, name %s\n", + PCI_VENDOR_ID_AMD, pci_info->err_dev, + pci_info->ctl_name); + return -ENODEV; + } + + if (pci_enable_device(pci_info->dev)) { + pci_dev_put(pci_info->dev); + printk(KERN_ERR "failed to enable:" + "vendor %x, device %x, name %s\n", + PCI_VENDOR_ID_AMD, pci_info->err_dev, + pci_info->ctl_name); + return -ENODEV; + } + + /* + * we do not allocate extra private structure for + * edac_pci_ctl_info, but make use of existing + * one instead. + */ + pci_info->edac_idx = edac_pci_alloc_index(); + pci_info->edac_dev = edac_pci_alloc_ctl_info(0, pci_info->ctl_name); + if (!pci_info->edac_dev) + return -ENOMEM; + + pci_info->edac_dev->pvt_info = pci_info; + pci_info->edac_dev->dev = &pci_info->dev->dev; + pci_info->edac_dev->mod_name = AMD8111_EDAC_MOD_STR; + pci_info->edac_dev->ctl_name = pci_info->ctl_name; + pci_info->edac_dev->dev_name = pci_info->dev->dev.bus_id; + + if (edac_op_state == EDAC_OPSTATE_POLL) + pci_info->edac_dev->edac_check = pci_info->check; + + if (pci_info->init) + pci_info->init(pci_info); + + if (edac_pci_add_device(pci_info->edac_dev, pci_info->edac_idx) > 0) { + printk(KERN_ERR "failed to add edac_pci for %s\n", + pci_info->ctl_name); + edac_pci_free_ctl_info(pci_info->edac_dev); + return -ENODEV; + } + + printk(KERN_INFO "added one edac_pci on AMD8111 " + "vendor %x, device %x, name %s\n", + PCI_VENDOR_ID_AMD, pci_info->err_dev, + pci_info->ctl_name); + + return 0; +} + +static void amd8111_pci_remove(struct pci_dev *dev) +{ + struct amd8111_pci_info *pci_info; + + for (pci_info = amd8111_pcis; pci_info->err_dev; pci_info++) + if (pci_info->dev->device == dev->device) + break; + + if (!pci_info->err_dev) /* should never happen */ + return; + + if (pci_info->edac_dev) { + edac_pci_del_device(pci_info->edac_dev->dev); + edac_pci_free_ctl_info(pci_info->edac_dev); + } + + if (pci_info->exit) + pci_info->exit(pci_info); + + pci_dev_put(pci_info->dev); +} + +/* PCI Device ID talbe for general EDAC device */ +static const struct pci_device_id amd8111_edac_dev_tbl[] = { + { + PCI_VEND_DEV(AMD, 8111_LPC), + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .class = 0, + .class_mask = 0, + .driver_data = LPC_BRIDGE, + }, + { + 0, + } /* table is NULL-terminated */ +}; +MODULE_DEVICE_TABLE(pci, amd8111_edac_dev_tbl); + +static struct pci_driver amd8111_edac_dev_driver = { + .name = "AMD8111_EDAC_DEV", + .probe = amd8111_dev_probe, + .remove = amd8111_dev_remove, + .id_table = amd8111_edac_dev_tbl, +}; + +/* PCI Device ID table for EDAC PCI controller */ +static const struct pci_device_id amd8111_edac_pci_tbl[] = { + { + PCI_VEND_DEV(AMD, 8111_PCI), + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .class = 0, + .class_mask = 0, + .driver_data = PCI_BRIDGE, + }, + { + 0, + } /* table is NULL-terminated */ +}; +MODULE_DEVICE_TABLE(pci, amd8111_edac_pci_tbl); + +static struct pci_driver amd8111_edac_pci_driver = { + .name = "AMD8111_EDAC_PCI", + .probe = amd8111_pci_probe, + .remove = amd8111_pci_remove, + .id_table = amd8111_edac_pci_tbl, +}; + +static int __init amd8111_edac_init(void) +{ + int val; + + printk(KERN_INFO "AMD8111 EDAC driver " AMD8111_EDAC_REVISION "\n"); + printk(KERN_INFO "\t(c) 2008 Wind River Systems, Inc.\n"); + + /* Only POLL mode supported so far */ + edac_op_state = EDAC_OPSTATE_POLL; + + val = pci_register_driver(&amd8111_edac_dev_driver); + val |= pci_register_driver(&amd8111_edac_pci_driver); + + return val; +} + +static void __exit amd8111_edac_exit(void) +{ + pci_unregister_driver(&amd8111_edac_pci_driver); + pci_unregister_driver(&amd8111_edac_dev_driver); +} + + +module_init(amd8111_edac_init); +module_exit(amd8111_edac_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cao Qingtao <qingtao.cao@windriver.com>\n"); +MODULE_DESCRIPTION("AMD8111 HyperTransport I/O Hub EDAC kernel module"); diff --git a/drivers/edac/amd8111_edac.h b/drivers/edac/amd8111_edac.h new file mode 100644 index 000000000000..35794331debc --- /dev/null +++ b/drivers/edac/amd8111_edac.h @@ -0,0 +1,130 @@ +/* + * amd8111_edac.h, EDAC defs for AMD8111 hypertransport chip + * + * Copyright (c) 2008 Wind River Systems, Inc. + * + * Authors: Cao Qingtao <qingtao.cao@windriver.com> + * Benjamin Walsh <benjamin.walsh@windriver.com> + * Hu Yongqi <yongqi.hu@windriver.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _AMD8111_EDAC_H_ +#define _AMD8111_EDAC_H_ + +/************************************************************ + * PCI Bridge Status and Command Register, DevA:0x04 + ************************************************************/ +#define REG_PCI_STSCMD 0x04 +enum pci_stscmd_bits { + PCI_STSCMD_SSE = BIT(30), + PCI_STSCMD_RMA = BIT(29), + PCI_STSCMD_RTA = BIT(28), + PCI_STSCMD_SERREN = BIT(8), + PCI_STSCMD_CLEAR_MASK = (PCI_STSCMD_SSE | + PCI_STSCMD_RMA | + PCI_STSCMD_RTA) +}; + +/************************************************************ + * PCI Bridge Memory Base-Limit Register, DevA:0x1c + ************************************************************/ +#define REG_MEM_LIM 0x1c +enum mem_limit_bits { + MEM_LIMIT_DPE = BIT(31), + MEM_LIMIT_RSE = BIT(30), + MEM_LIMIT_RMA = BIT(29), + MEM_LIMIT_RTA = BIT(28), + MEM_LIMIT_STA = BIT(27), + MEM_LIMIT_MDPE = BIT(24), + MEM_LIMIT_CLEAR_MASK = (MEM_LIMIT_DPE | + MEM_LIMIT_RSE | + MEM_LIMIT_RMA | + MEM_LIMIT_RTA | + MEM_LIMIT_STA | + MEM_LIMIT_MDPE) +}; + +/************************************************************ + * HyperTransport Link Control Register, DevA:0xc4 + ************************************************************/ +#define REG_HT_LINK 0xc4 +enum ht_link_bits { + HT_LINK_LKFAIL = BIT(4), + HT_LINK_CRCFEN = BIT(1), + HT_LINK_CLEAR_MASK = (HT_LINK_LKFAIL) +}; + +/************************************************************ + * PCI Bridge Interrupt and Bridge Control, DevA:0x3c + ************************************************************/ +#define REG_PCI_INTBRG_CTRL 0x3c +enum pci_intbrg_ctrl_bits { + PCI_INTBRG_CTRL_DTSERREN = BIT(27), + PCI_INTBRG_CTRL_DTSTAT = BIT(26), + PCI_INTBRG_CTRL_MARSP = BIT(21), + PCI_INTBRG_CTRL_SERREN = BIT(17), + PCI_INTBRG_CTRL_PEREN = BIT(16), + PCI_INTBRG_CTRL_CLEAR_MASK = (PCI_INTBRG_CTRL_DTSTAT), + PCI_INTBRG_CTRL_POLL_MASK = (PCI_INTBRG_CTRL_DTSERREN | + PCI_INTBRG_CTRL_MARSP | + PCI_INTBRG_CTRL_SERREN) +}; + +/************************************************************ + * I/O Control 1 Register, DevB:0x40 + ************************************************************/ +#define REG_IO_CTRL_1 0x40 +enum io_ctrl_1_bits { + IO_CTRL_1_NMIONERR = BIT(7), + IO_CTRL_1_LPC_ERR = BIT(6), + IO_CTRL_1_PW2LPC = BIT(1), + IO_CTRL_1_CLEAR_MASK = (IO_CTRL_1_LPC_ERR | IO_CTRL_1_PW2LPC) +}; + +/************************************************************ + * Legacy I/O Space Registers + ************************************************************/ +#define REG_AT_COMPAT 0x61 +enum at_compat_bits { + AT_COMPAT_SERR = BIT(7), + AT_COMPAT_IOCHK = BIT(6), + AT_COMPAT_CLRIOCHK = BIT(3), + AT_COMPAT_CLRSERR = BIT(2), +}; + +struct amd8111_dev_info { + u16 err_dev; /* PCI Device ID */ + struct pci_dev *dev; + int edac_idx; /* device index */ + char *ctl_name; + struct edac_device_ctl_info *edac_dev; + void (*init)(struct amd8111_dev_info *dev_info); + void (*exit)(struct amd8111_dev_info *dev_info); + void (*check)(struct edac_device_ctl_info *edac_dev); +}; + +struct amd8111_pci_info { + u16 err_dev; /* PCI Device ID */ + struct pci_dev *dev; + int edac_idx; /* pci index */ + const char *ctl_name; + struct edac_pci_ctl_info *edac_dev; + void (*init)(struct amd8111_pci_info *dev_info); + void (*exit)(struct amd8111_pci_info *dev_info); + void (*check)(struct edac_pci_ctl_info *edac_dev); +}; + +#endif /* _AMD8111_EDAC_H_ */ diff --git a/drivers/edac/amd8131_edac.c b/drivers/edac/amd8131_edac.c new file mode 100644 index 000000000000..c083b31cac5a --- /dev/null +++ b/drivers/edac/amd8131_edac.c @@ -0,0 +1,379 @@ +/* + * amd8131_edac.c, AMD8131 hypertransport chip EDAC kernel module + * + * Copyright (c) 2008 Wind River Systems, Inc. + * + * Authors: Cao Qingtao <qingtao.cao@windriver.com> + * Benjamin Walsh <benjamin.walsh@windriver.com> + * Hu Yongqi <yongqi.hu@windriver.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License 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/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/edac.h> +#include <linux/pci_ids.h> + +#include "edac_core.h" +#include "edac_module.h" +#include "amd8131_edac.h" + +#define AMD8131_EDAC_REVISION " Ver: 1.0.0 " __DATE__ +#define AMD8131_EDAC_MOD_STR "amd8131_edac" + +/* Wrapper functions for accessing PCI configuration space */ +static void edac_pci_read_dword(struct pci_dev *dev, int reg, u32 *val32) +{ + int ret; + + ret = pci_read_config_dword(dev, reg, val32); + if (ret != 0) + printk(KERN_ERR AMD8131_EDAC_MOD_STR + " PCI Access Read Error at 0x%x\n", reg); +} + +static void edac_pci_write_dword(struct pci_dev *dev, int reg, u32 val32) +{ + int ret; + + ret = pci_write_config_dword(dev, reg, val32); + if (ret != 0) + printk(KERN_ERR AMD8131_EDAC_MOD_STR + " PCI Access Write Error at 0x%x\n", reg); +} + +static char * const bridge_str[] = { + [NORTH_A] = "NORTH A", + [NORTH_B] = "NORTH B", + [SOUTH_A] = "SOUTH A", + [SOUTH_B] = "SOUTH B", + [NO_BRIDGE] = "NO BRIDGE", +}; + +/* Support up to two AMD8131 chipsets on a platform */ +static struct amd8131_dev_info amd8131_devices[] = { + { + .inst = NORTH_A, + .devfn = DEVFN_PCIX_BRIDGE_NORTH_A, + .ctl_name = "AMD8131_PCIX_NORTH_A", + }, + { + .inst = NORTH_B, + .devfn = DEVFN_PCIX_BRIDGE_NORTH_B, + .ctl_name = "AMD8131_PCIX_NORTH_B", + }, + { + .inst = SOUTH_A, + .devfn = DEVFN_PCIX_BRIDGE_SOUTH_A, + .ctl_name = "AMD8131_PCIX_SOUTH_A", + }, + { + .inst = SOUTH_B, + .devfn = DEVFN_PCIX_BRIDGE_SOUTH_B, + .ctl_name = "AMD8131_PCIX_SOUTH_B", + }, + {.inst = NO_BRIDGE,}, +}; + +static void amd8131_pcix_init(struct amd8131_dev_info *dev_info) +{ + u32 val32; + struct pci_dev *dev = dev_info->dev; + + /* First clear error detection flags */ + edac_pci_read_dword(dev, REG_MEM_LIM, &val32); + if (val32 & MEM_LIMIT_MASK) + edac_pci_write_dword(dev, REG_MEM_LIM, val32); + + /* Clear Discard Timer Timedout flag */ + edac_pci_read_dword(dev, REG_INT_CTLR, &val32); + if (val32 & INT_CTLR_DTS) + edac_pci_write_dword(dev, REG_INT_CTLR, val32); + + /* Clear CRC Error flag on link side A */ + edac_pci_read_dword(dev, REG_LNK_CTRL_A, &val32); + if (val32 & LNK_CTRL_CRCERR_A) + edac_pci_write_dword(dev, REG_LNK_CTRL_A, val32); + + /* Clear CRC Error flag on link side B */ + edac_pci_read_dword(dev, REG_LNK_CTRL_B, &val32); + if (val32 & LNK_CTRL_CRCERR_B) + edac_pci_write_dword(dev, REG_LNK_CTRL_B, val32); + + /* + * Then enable all error detections. + * + * Setup Discard Timer Sync Flood Enable, + * System Error Enable and Parity Error Enable. + */ + edac_pci_read_dword(dev, REG_INT_CTLR, &val32); + val32 |= INT_CTLR_PERR | INT_CTLR_SERR | INT_CTLR_DTSE; + edac_pci_write_dword(dev, REG_INT_CTLR, val32); + + /* Enable overall SERR Error detection */ + edac_pci_read_dword(dev, REG_STS_CMD, &val32); + val32 |= STS_CMD_SERREN; + edac_pci_write_dword(dev, REG_STS_CMD, val32); + + /* Setup CRC Flood Enable for link side A */ + edac_pci_read_dword(dev, REG_LNK_CTRL_A, &val32); + val32 |= LNK_CTRL_CRCFEN; + edac_pci_write_dword(dev, REG_LNK_CTRL_A, val32); + + /* Setup CRC Flood Enable for link side B */ + edac_pci_read_dword(dev, REG_LNK_CTRL_B, &val32); + val32 |= LNK_CTRL_CRCFEN; + edac_pci_write_dword(dev, REG_LNK_CTRL_B, val32); +} + +static void amd8131_pcix_exit(struct amd8131_dev_info *dev_info) +{ + u32 val32; + struct pci_dev *dev = dev_info->dev; + + /* Disable SERR, PERR and DTSE Error detection */ + edac_pci_read_dword(dev, REG_INT_CTLR, &val32); + val32 &= ~(INT_CTLR_PERR | INT_CTLR_SERR | INT_CTLR_DTSE); + edac_pci_write_dword(dev, REG_INT_CTLR, val32); + + /* Disable overall System Error detection */ + edac_pci_read_dword(dev, REG_STS_CMD, &val32); + val32 &= ~STS_CMD_SERREN; + edac_pci_write_dword(dev, REG_STS_CMD, val32); + + /* Disable CRC Sync Flood on link side A */ + edac_pci_read_dword(dev, REG_LNK_CTRL_A, &val32); + val32 &= ~LNK_CTRL_CRCFEN; + edac_pci_write_dword(dev, REG_LNK_CTRL_A, val32); + + /* Disable CRC Sync Flood on link side B */ + edac_pci_read_dword(dev, REG_LNK_CTRL_B, &val32); + val32 &= ~LNK_CTRL_CRCFEN; + edac_pci_write_dword(dev, REG_LNK_CTRL_B, val32); +} + +static void amd8131_pcix_check(struct edac_pci_ctl_info *edac_dev) +{ + struct amd8131_dev_info *dev_info = edac_dev->pvt_info; + struct pci_dev *dev = dev_info->dev; + u32 val32; + + /* Check PCI-X Bridge Memory Base-Limit Register for errors */ + edac_pci_read_dword(dev, REG_MEM_LIM, &val32); + if (val32 & MEM_LIMIT_MASK) { + printk(KERN_INFO "Error(s) in mem limit register " + "on %s bridge\n", dev_info->ctl_name); + printk(KERN_INFO "DPE: %d, RSE: %d, RMA: %d\n" + "RTA: %d, STA: %d, MDPE: %d\n", + val32 & MEM_LIMIT_DPE, + val32 & MEM_LIMIT_RSE, + val32 & MEM_LIMIT_RMA, + val32 & MEM_LIMIT_RTA, + val32 & MEM_LIMIT_STA, + val32 & MEM_LIMIT_MDPE); + + val32 |= MEM_LIMIT_MASK; + edac_pci_write_dword(dev, REG_MEM_LIM, val32); + + edac_pci_handle_npe(edac_dev, edac_dev->ctl_name); + } + + /* Check if Discard Timer timed out */ + edac_pci_read_dword(dev, REG_INT_CTLR, &val32); + if (val32 & INT_CTLR_DTS) { + printk(KERN_INFO "Error(s) in interrupt and control register " + "on %s bridge\n", dev_info->ctl_name); + printk(KERN_INFO "DTS: %d\n", val32 & INT_CTLR_DTS); + + val32 |= INT_CTLR_DTS; + edac_pci_write_dword(dev, REG_INT_CTLR, val32); + + edac_pci_handle_npe(edac_dev, edac_dev->ctl_name); + } + + /* Check if CRC error happens on link side A */ + edac_pci_read_dword(dev, REG_LNK_CTRL_A, &val32); + if (val32 & LNK_CTRL_CRCERR_A) { + printk(KERN_INFO "Error(s) in link conf and control register " + "on %s bridge\n", dev_info->ctl_name); + printk(KERN_INFO "CRCERR: %d\n", val32 & LNK_CTRL_CRCERR_A); + + val32 |= LNK_CTRL_CRCERR_A; + edac_pci_write_dword(dev, REG_LNK_CTRL_A, val32); + + edac_pci_handle_npe(edac_dev, edac_dev->ctl_name); + } + + /* Check if CRC error happens on link side B */ + edac_pci_read_dword(dev, REG_LNK_CTRL_B, &val32); + if (val32 & LNK_CTRL_CRCERR_B) { + printk(KERN_INFO "Error(s) in link conf and control register " + "on %s bridge\n", dev_info->ctl_name); + printk(KERN_INFO "CRCERR: %d\n", val32 & LNK_CTRL_CRCERR_B); + + val32 |= LNK_CTRL_CRCERR_B; + edac_pci_write_dword(dev, REG_LNK_CTRL_B, val32); + + edac_pci_handle_npe(edac_dev, edac_dev->ctl_name); + } +} + +static struct amd8131_info amd8131_chipset = { + .err_dev = PCI_DEVICE_ID_AMD_8131_APIC, + .devices = amd8131_devices, + .init = amd8131_pcix_init, + .exit = amd8131_pcix_exit, + .check = amd8131_pcix_check, +}; + +/* + * There are 4 PCIX Bridges on ATCA-6101 that share the same PCI Device ID, + * so amd8131_probe() would be called by kernel 4 times, with different + * address of pci_dev for each of them each time. + */ +static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + struct amd8131_dev_info *dev_info; + + for (dev_info = amd8131_chipset.devices; dev_info->inst != NO_BRIDGE; + dev_info++) + if (dev_info->devfn == dev->devfn) + break; + + if (dev_info->inst == NO_BRIDGE) /* should never happen */ + return -ENODEV; + + /* + * We can't call pci_get_device() as we are used to do because + * there are 4 of them but pci_dev_get() instead. + */ + dev_info->dev = pci_dev_get(dev); + + if (pci_enable_device(dev_info->dev)) { + pci_dev_put(dev_info->dev); + printk(KERN_ERR "failed to enable:" + "vendor %x, device %x, devfn %x, name %s\n", + PCI_VENDOR_ID_AMD, amd8131_chipset.err_dev, + dev_info->devfn, dev_info->ctl_name); + return -ENODEV; + } + + /* + * we do not allocate extra private structure for + * edac_pci_ctl_info, but make use of existing + * one instead. + */ + dev_info->edac_idx = edac_pci_alloc_index(); + dev_info->edac_dev = edac_pci_alloc_ctl_info(0, dev_info->ctl_name); + if (!dev_info->edac_dev) + return -ENOMEM; + + dev_info->edac_dev->pvt_info = dev_info; + dev_info->edac_dev->dev = &dev_info->dev->dev; + dev_info->edac_dev->mod_name = AMD8131_EDAC_MOD_STR; + dev_info->edac_dev->ctl_name = dev_info->ctl_name; + dev_info->edac_dev->dev_name = dev_info->dev->dev.bus_id; + + if (edac_op_state == EDAC_OPSTATE_POLL) + dev_info->edac_dev->edac_check = amd8131_chipset.check; + + if (amd8131_chipset.init) + amd8131_chipset.init(dev_info); + + if (edac_pci_add_device(dev_info->edac_dev, dev_info->edac_idx) > 0) { + printk(KERN_ERR "failed edac_pci_add_device() for %s\n", + dev_info->ctl_name); + edac_pci_free_ctl_info(dev_info->edac_dev); + return -ENODEV; + } + + printk(KERN_INFO "added one device on AMD8131 " + "vendor %x, device %x, devfn %x, name %s\n", + PCI_VENDOR_ID_AMD, amd8131_chipset.err_dev, + dev_info->devfn, dev_info->ctl_name); + + return 0; +} + +static void amd8131_remove(struct pci_dev *dev) +{ + struct amd8131_dev_info *dev_info; + + for (dev_info = amd8131_chipset.devices; dev_info->inst != NO_BRIDGE; + dev_info++) + if (dev_info->devfn == dev->devfn) + break; + + if (dev_info->inst == NO_BRIDGE) /* should never happen */ + return; + + if (dev_info->edac_dev) { + edac_pci_del_device(dev_info->edac_dev->dev); + edac_pci_free_ctl_info(dev_info->edac_dev); + } + + if (amd8131_chipset.exit) + amd8131_chipset.exit(dev_info); + + pci_dev_put(dev_info->dev); +} + +static const struct pci_device_id amd8131_edac_pci_tbl[] = { + { + PCI_VEND_DEV(AMD, 8131_BRIDGE), + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + .class = 0, + .class_mask = 0, + .driver_data = 0, + }, + { + 0, + } /* table is NULL-terminated */ +}; +MODULE_DEVICE_TABLE(pci, amd8131_edac_pci_tbl); + +static struct pci_driver amd8131_edac_driver = { + .name = AMD8131_EDAC_MOD_STR, + .probe = amd8131_probe, + .remove = amd8131_remove, + .id_table = amd8131_edac_pci_tbl, +}; + +static int __init amd8131_edac_init(void) +{ + printk(KERN_INFO "AMD8131 EDAC driver " AMD8131_EDAC_REVISION "\n"); + printk(KERN_INFO "\t(c) 2008 Wind River Systems, Inc.\n"); + + /* Only POLL mode supported so far */ + edac_op_state = EDAC_OPSTATE_POLL; + + return pci_register_driver(&amd8131_edac_driver); +} + +static void __exit amd8131_edac_exit(void) +{ + pci_unregister_driver(&amd8131_edac_driver); +} + +module_init(amd8131_edac_init); +module_exit(amd8131_edac_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cao Qingtao <qingtao.cao@windriver.com>\n"); +MODULE_DESCRIPTION("AMD8131 HyperTransport PCI-X Tunnel EDAC kernel module"); diff --git a/drivers/edac/amd8131_edac.h b/drivers/edac/amd8131_edac.h new file mode 100644 index 000000000000..60e0d1c72dee --- /dev/null +++ b/drivers/edac/amd8131_edac.h @@ -0,0 +1,119 @@ +/* + * amd8131_edac.h, EDAC defs for AMD8131 hypertransport chip + * + * Copyright (c) 2008 Wind River Systems, Inc. + * + * Authors: Cao Qingtao <qingtao.cao@windriver.com> + * Benjamin Walsh <benjamin.walsh@windriver.com> + * Hu Yongqi <yongqi.hu@windriver.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _AMD8131_EDAC_H_ +#define _AMD8131_EDAC_H_ + +#define DEVFN_PCIX_BRIDGE_NORTH_A 8 +#define DEVFN_PCIX_BRIDGE_NORTH_B 16 +#define DEVFN_PCIX_BRIDGE_SOUTH_A 24 +#define DEVFN_PCIX_BRIDGE_SOUTH_B 32 + +/************************************************************ + * PCI-X Bridge Status and Command Register, DevA:0x04 + ************************************************************/ +#define REG_STS_CMD 0x04 +enum sts_cmd_bits { + STS_CMD_SSE = BIT(30), + STS_CMD_SERREN = BIT(8) +}; + +/************************************************************ + * PCI-X Bridge Interrupt and Bridge Control Register, + ************************************************************/ +#define REG_INT_CTLR 0x3c +enum int_ctlr_bits { + INT_CTLR_DTSE = BIT(27), + INT_CTLR_DTS = BIT(26), + INT_CTLR_SERR = BIT(17), + INT_CTLR_PERR = BIT(16) +}; + +/************************************************************ + * PCI-X Bridge Memory Base-Limit Register, DevA:0x1C + ************************************************************/ +#define REG_MEM_LIM 0x1c +enum mem_limit_bits { + MEM_LIMIT_DPE = BIT(31), + MEM_LIMIT_RSE = BIT(30), + MEM_LIMIT_RMA = BIT(29), + MEM_LIMIT_RTA = BIT(28), + MEM_LIMIT_STA = BIT(27), + MEM_LIMIT_MDPE = BIT(24), + MEM_LIMIT_MASK = MEM_LIMIT_DPE|MEM_LIMIT_RSE|MEM_LIMIT_RMA| + MEM_LIMIT_RTA|MEM_LIMIT_STA|MEM_LIMIT_MDPE +}; + +/************************************************************ + * Link Configuration And Control Register, side A + ************************************************************/ +#define REG_LNK_CTRL_A 0xc4 + +/************************************************************ + * Link Configuration And Control Register, side B + ************************************************************/ +#define REG_LNK_CTRL_B 0xc8 + +enum lnk_ctrl_bits { + LNK_CTRL_CRCERR_A = BIT(9), + LNK_CTRL_CRCERR_B = BIT(8), + LNK_CTRL_CRCFEN = BIT(1) +}; + +enum pcix_bridge_inst { + NORTH_A = 0, + NORTH_B = 1, + SOUTH_A = 2, + SOUTH_B = 3, + NO_BRIDGE = 4 +}; + +struct amd8131_dev_info { + int devfn; + enum pcix_bridge_inst inst; + struct pci_dev *dev; + int edac_idx; /* pci device index */ + char *ctl_name; + struct edac_pci_ctl_info *edac_dev; +}; + +/* + * AMD8131 chipset has two pairs of PCIX Bridge and related IOAPIC + * Controler, and ATCA-6101 has two AMD8131 chipsets, so there are + * four PCIX Bridges on ATCA-6101 altogether. + * + * These PCIX Bridges share the same PCI Device ID and are all of + * Function Zero, they could be discrimated by their pci_dev->devfn. + * They share the same set of init/check/exit methods, and their + * private structures are collected in the devices[] array. + */ +struct amd8131_info { + u16 err_dev; /* PCI Device ID for AMD8131 APIC*/ + struct amd8131_dev_info *devices; + void (*init)(struct amd8131_dev_info *dev_info); + void (*exit)(struct amd8131_dev_info *dev_info); + void (*check)(struct edac_pci_ctl_info *edac_dev); +}; + +#endif /* _AMD8131_EDAC_H_ */ + diff --git a/drivers/edac/cell_edac.c b/drivers/edac/cell_edac.c index 24f3ca851523..cb0f639f049d 100644 --- a/drivers/edac/cell_edac.c +++ b/drivers/edac/cell_edac.c @@ -198,7 +198,7 @@ static int __devinit cell_edac_probe(struct platform_device *pdev) mci->edac_cap = EDAC_FLAG_EC | EDAC_FLAG_SECDED; mci->mod_name = "cell_edac"; mci->ctl_name = "MIC"; - mci->dev_name = pdev->dev.bus_id; + mci->dev_name = dev_name(&pdev->dev); mci->edac_check = cell_edac_check; cell_edac_init_csrows(mci); diff --git a/drivers/edac/edac_core.h b/drivers/edac/edac_core.h index 4b55ec607a88..28f2c3f959b5 100644 --- a/drivers/edac/edac_core.h +++ b/drivers/edac/edac_core.h @@ -49,6 +49,10 @@ #define edac_printk(level, prefix, fmt, arg...) \ printk(level "EDAC " prefix ": " fmt, ##arg) +#define edac_printk_verbose(level, prefix, fmt, arg...) \ + printk(level "EDAC " prefix ": " "in %s, line at %d: " fmt, \ + __FILE__, __LINE__, ##arg) + #define edac_mc_printk(mci, level, fmt, arg...) \ printk(level "EDAC MC%d: " fmt, mci->mc_idx, ##arg) @@ -71,11 +75,20 @@ #ifdef CONFIG_EDAC_DEBUG extern int edac_debug_level; +#ifndef CONFIG_EDAC_DEBUG_VERBOSE #define edac_debug_printk(level, fmt, arg...) \ do { \ if (level <= edac_debug_level) \ edac_printk(KERN_DEBUG, EDAC_DEBUG, fmt, ##arg); \ - } while(0) + } while (0) +#else /* CONFIG_EDAC_DEBUG_VERBOSE */ +#define edac_debug_printk(level, fmt, arg...) \ + do { \ + if (level <= edac_debug_level) \ + edac_printk_verbose(KERN_DEBUG, EDAC_DEBUG, fmt, \ + ##arg); \ + } while (0) +#endif #define debugf0( ... ) edac_debug_printk(0, __VA_ARGS__ ) #define debugf1( ... ) edac_debug_printk(1, __VA_ARGS__ ) @@ -831,6 +844,7 @@ extern void edac_pci_free_ctl_info(struct edac_pci_ctl_info *pci); extern void edac_pci_reset_delay_period(struct edac_pci_ctl_info *pci, unsigned long value); +extern int edac_pci_alloc_index(void); extern int edac_pci_add_device(struct edac_pci_ctl_info *pci, int edac_idx); extern struct edac_pci_ctl_info *edac_pci_del_device(struct device *dev); diff --git a/drivers/edac/edac_pci.c b/drivers/edac/edac_pci.c index 5d3c8083a40e..5b150aea703a 100644 --- a/drivers/edac/edac_pci.c +++ b/drivers/edac/edac_pci.c @@ -30,6 +30,7 @@ static DEFINE_MUTEX(edac_pci_ctls_mutex); static LIST_HEAD(edac_pci_list); +static atomic_t pci_indexes = ATOMIC_INIT(0); /* * edac_pci_alloc_ctl_info @@ -318,6 +319,19 @@ void edac_pci_reset_delay_period(struct edac_pci_ctl_info *pci, EXPORT_SYMBOL_GPL(edac_pci_reset_delay_period); /* + * edac_pci_alloc_index: Allocate a unique PCI index number + * + * Return: + * allocated index number + * + */ +int edac_pci_alloc_index(void) +{ + return atomic_inc_return(&pci_indexes) - 1; +} +EXPORT_SYMBOL_GPL(edac_pci_alloc_index); + +/* * edac_pci_add_device: Insert the 'edac_dev' structure into the * edac_pci global list and create sysfs entries associated with * edac_pci structure. diff --git a/drivers/edac/mpc85xx_edac.c b/drivers/edac/mpc85xx_edac.c index 853ef37ec006..4637a4a757df 100644 --- a/drivers/edac/mpc85xx_edac.c +++ b/drivers/edac/mpc85xx_edac.c @@ -218,7 +218,7 @@ static int __devinit mpc85xx_pci_err_probe(struct of_device *op, pci->dev = &op->dev; pci->mod_name = EDAC_MOD_STR; pci->ctl_name = pdata->name; - pci->dev_name = op->dev.bus_id; + pci->dev_name = dev_name(&op->dev); if (edac_op_state == EDAC_OPSTATE_POLL) pci->edac_check = mpc85xx_pci_check; diff --git a/drivers/edac/mv64x60_edac.c b/drivers/edac/mv64x60_edac.c index 083ce8d0c63d..5131aaae8e03 100644 --- a/drivers/edac/mv64x60_edac.c +++ b/drivers/edac/mv64x60_edac.c @@ -121,7 +121,7 @@ static int __devinit mv64x60_pci_err_probe(struct platform_device *pdev) pdata->irq = NO_IRQ; platform_set_drvdata(pdev, pci); pci->dev = &pdev->dev; - pci->dev_name = pdev->dev.bus_id; + pci->dev_name = dev_name(&pdev->dev); pci->mod_name = EDAC_MOD_STR; pci->ctl_name = pdata->name; @@ -294,7 +294,7 @@ static int __devinit mv64x60_sram_err_probe(struct platform_device *pdev) pdata->irq = NO_IRQ; edac_dev->dev = &pdev->dev; platform_set_drvdata(pdev, edac_dev); - edac_dev->dev_name = pdev->dev.bus_id; + edac_dev->dev_name = dev_name(&pdev->dev); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!r) { @@ -462,7 +462,7 @@ static int __devinit mv64x60_cpu_err_probe(struct platform_device *pdev) pdata->irq = NO_IRQ; edac_dev->dev = &pdev->dev; platform_set_drvdata(pdev, edac_dev); - edac_dev->dev_name = pdev->dev.bus_id; + edac_dev->dev_name = dev_name(&pdev->dev); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!r) { @@ -713,7 +713,7 @@ static int __devinit mv64x60_mc_err_probe(struct platform_device *pdev) platform_set_drvdata(pdev, mci); pdata->name = "mv64x60_mc_err"; pdata->irq = NO_IRQ; - mci->dev_name = pdev->dev.bus_id; + mci->dev_name = dev_name(&pdev->dev); pdata->edac_idx = edac_mc_idx++; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); diff --git a/drivers/edac/ppc4xx_edac.c b/drivers/edac/ppc4xx_edac.c new file mode 100644 index 000000000000..11f2172aa1e6 --- /dev/null +++ b/drivers/edac/ppc4xx_edac.c @@ -0,0 +1,1448 @@ +/* + * Copyright (c) 2008 Nuovation System Designs, LLC + * Grant Erickson <gerickson@nuovations.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; version 2 of the + * License. + * + */ + +#include <linux/edac.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_platform.h> +#include <linux/types.h> + +#include <asm/dcr.h> + +#include "edac_core.h" +#include "ppc4xx_edac.h" + +/* + * This file implements a driver for monitoring and handling events + * associated with the IMB DDR2 ECC controller found in the AMCC/IBM + * 405EX[r], 440SP, 440SPe, 460EX, 460GT and 460SX. + * + * As realized in the 405EX[r], this controller features: + * + * - Support for registered- and non-registered DDR1 and DDR2 memory. + * - 32-bit or 16-bit memory interface with optional ECC. + * + * o ECC support includes: + * + * - 4-bit SEC/DED + * - Aligned-nibble error detect + * - Bypass mode + * + * - Two (2) memory banks/ranks. + * - Up to 1 GiB per bank/rank in 32-bit mode and up to 512 MiB per + * bank/rank in 16-bit mode. + * + * As realized in the 440SP and 440SPe, this controller changes/adds: + * + * - 64-bit or 32-bit memory interface with optional ECC. + * + * o ECC support includes: + * + * - 8-bit SEC/DED + * - Aligned-nibble error detect + * - Bypass mode + * + * - Up to 4 GiB per bank/rank in 64-bit mode and up to 2 GiB + * per bank/rank in 32-bit mode. + * + * As realized in the 460EX and 460GT, this controller changes/adds: + * + * - 64-bit or 32-bit memory interface with optional ECC. + * + * o ECC support includes: + * + * - 8-bit SEC/DED + * - Aligned-nibble error detect + * - Bypass mode + * + * - Four (4) memory banks/ranks. + * - Up to 16 GiB per bank/rank in 64-bit mode and up to 8 GiB + * per bank/rank in 32-bit mode. + * + * At present, this driver has ONLY been tested against the controller + * realization in the 405EX[r] on the AMCC Kilauea and Haleakala + * boards (256 MiB w/o ECC memory soldered onto the board) and a + * proprietary board based on those designs (128 MiB ECC memory, also + * soldered onto the board). + * + * Dynamic feature detection and handling needs to be added for the + * other realizations of this controller listed above. + * + * Eventually, this driver will likely be adapted to the above variant + * realizations of this controller as well as broken apart to handle + * the other known ECC-capable controllers prevalent in other 4xx + * processors: + * + * - IBM SDRAM (405GP, 405CR and 405EP) "ibm,sdram-4xx" + * - IBM DDR1 (440GP, 440GX, 440EP and 440GR) "ibm,sdram-4xx-ddr" + * - Denali DDR1/DDR2 (440EPX and 440GRX) "denali,sdram-4xx-ddr2" + * + * For this controller, unfortunately, correctable errors report + * nothing more than the beat/cycle and byte/lane the correction + * occurred on and the check bit group that covered the error. + * + * In contrast, uncorrectable errors also report the failing address, + * the bus master and the transaction direction (i.e. read or write) + * + * Regardless of whether the error is a CE or a UE, we report the + * following pieces of information in the driver-unique message to the + * EDAC subsystem: + * + * - Device tree path + * - Bank(s) + * - Check bit error group + * - Beat(s)/lane(s) + */ + +/* Preprocessor Definitions */ + +#define EDAC_OPSTATE_INT_STR "interrupt" +#define EDAC_OPSTATE_POLL_STR "polled" +#define EDAC_OPSTATE_UNKNOWN_STR "unknown" + +#define PPC4XX_EDAC_MODULE_NAME "ppc4xx_edac" +#define PPC4XX_EDAC_MODULE_REVISION "v1.0.0 " __DATE__ + +#define PPC4XX_EDAC_MESSAGE_SIZE 256 + +/* + * Kernel logging without an EDAC instance + */ +#define ppc4xx_edac_printk(level, fmt, arg...) \ + edac_printk(level, "PPC4xx MC", fmt, ##arg) + +/* + * Kernel logging with an EDAC instance + */ +#define ppc4xx_edac_mc_printk(level, mci, fmt, arg...) \ + edac_mc_chipset_printk(mci, level, "PPC4xx", fmt, ##arg) + +/* + * Macros to convert bank configuration size enumerations into MiB and + * page values. + */ +#define SDRAM_MBCF_SZ_MiB_MIN 4 +#define SDRAM_MBCF_SZ_TO_MiB(n) (SDRAM_MBCF_SZ_MiB_MIN \ + << (SDRAM_MBCF_SZ_DECODE(n))) +#define SDRAM_MBCF_SZ_TO_PAGES(n) (SDRAM_MBCF_SZ_MiB_MIN \ + << (20 - PAGE_SHIFT + \ + SDRAM_MBCF_SZ_DECODE(n))) + +/* + * The ibm,sdram-4xx-ddr2 Device Control Registers (DCRs) are + * indirectly acccessed and have a base and length defined by the + * device tree. The base can be anything; however, we expect the + * length to be precisely two registers, the first for the address + * window and the second for the data window. + */ +#define SDRAM_DCR_RESOURCE_LEN 2 +#define SDRAM_DCR_ADDR_OFFSET 0 +#define SDRAM_DCR_DATA_OFFSET 1 + +/* + * Device tree interrupt indices + */ +#define INTMAP_ECCDED_INDEX 0 /* Double-bit Error Detect */ +#define INTMAP_ECCSEC_INDEX 1 /* Single-bit Error Correct */ + +/* Type Definitions */ + +/* + * PPC4xx SDRAM memory controller private instance data + */ +struct ppc4xx_edac_pdata { + dcr_host_t dcr_host; /* Indirect DCR address/data window mapping */ + struct { + int sec; /* Single-bit correctable error IRQ assigned */ + int ded; /* Double-bit detectable error IRQ assigned */ + } irqs; +}; + +/* + * Various status data gathered and manipulated when checking and + * reporting ECC status. + */ +struct ppc4xx_ecc_status { + u32 ecces; + u32 besr; + u32 bearh; + u32 bearl; + u32 wmirq; +}; + +/* Function Prototypes */ + +static int ppc4xx_edac_probe(struct of_device *device, + const struct of_device_id *device_id); +static int ppc4xx_edac_remove(struct of_device *device); + +/* Global Variables */ + +/* + * Device tree node type and compatible tuples this driver can match + * on. + */ +static struct of_device_id ppc4xx_edac_match[] = { + { + .compatible = "ibm,sdram-4xx-ddr2" + }, + { } +}; + +static struct of_platform_driver ppc4xx_edac_driver = { + .match_table = ppc4xx_edac_match, + .probe = ppc4xx_edac_probe, + .remove = ppc4xx_edac_remove, + .driver = { + .owner = THIS_MODULE, + .name = PPC4XX_EDAC_MODULE_NAME + } +}; + +/* + * TODO: The row and channel parameters likely need to be dynamically + * set based on the aforementioned variant controller realizations. + */ +static const unsigned ppc4xx_edac_nr_csrows = 2; +static const unsigned ppc4xx_edac_nr_chans = 1; + +/* + * Strings associated with PLB master IDs capable of being posted in + * SDRAM_BESR or SDRAM_WMIRQ on uncorrectable ECC errors. + */ +static const char * const ppc4xx_plb_masters[9] = { + [SDRAM_PLB_M0ID_ICU] = "ICU", + [SDRAM_PLB_M0ID_PCIE0] = "PCI-E 0", + [SDRAM_PLB_M0ID_PCIE1] = "PCI-E 1", + [SDRAM_PLB_M0ID_DMA] = "DMA", + [SDRAM_PLB_M0ID_DCU] = "DCU", + [SDRAM_PLB_M0ID_OPB] = "OPB", + [SDRAM_PLB_M0ID_MAL] = "MAL", + [SDRAM_PLB_M0ID_SEC] = "SEC", + [SDRAM_PLB_M0ID_AHB] = "AHB" +}; + +/** + * mfsdram - read and return controller register data + * @dcr_host: A pointer to the DCR mapping. + * @idcr_n: The indirect DCR register to read. + * + * This routine reads and returns the data associated with the + * controller's specified indirect DCR register. + * + * Returns the read data. + */ +static inline u32 +mfsdram(const dcr_host_t *dcr_host, unsigned int idcr_n) +{ + return __mfdcri(dcr_host->base + SDRAM_DCR_ADDR_OFFSET, + dcr_host->base + SDRAM_DCR_DATA_OFFSET, + idcr_n); +} + +/** + * mtsdram - write controller register data + * @dcr_host: A pointer to the DCR mapping. + * @idcr_n: The indirect DCR register to write. + * @value: The data to write. + * + * This routine writes the provided data to the controller's specified + * indirect DCR register. + */ +static inline void +mtsdram(const dcr_host_t *dcr_host, unsigned int idcr_n, u32 value) +{ + return __mtdcri(dcr_host->base + SDRAM_DCR_ADDR_OFFSET, + dcr_host->base + SDRAM_DCR_DATA_OFFSET, + idcr_n, + value); +} + +/** + * ppc4xx_edac_check_bank_error - check a bank for an ECC bank error + * @status: A pointer to the ECC status structure to check for an + * ECC bank error. + * @bank: The bank to check for an ECC error. + * + * This routine determines whether the specified bank has an ECC + * error. + * + * Returns true if the specified bank has an ECC error; otherwise, + * false. + */ +static bool +ppc4xx_edac_check_bank_error(const struct ppc4xx_ecc_status *status, + unsigned int bank) +{ + switch (bank) { + case 0: + return status->ecces & SDRAM_ECCES_BK0ER; + case 1: + return status->ecces & SDRAM_ECCES_BK1ER; + default: + return false; + } +} + +/** + * ppc4xx_edac_generate_bank_message - generate interpretted bank status message + * @mci: A pointer to the EDAC memory controller instance associated + * with the bank message being generated. + * @status: A pointer to the ECC status structure to generate the + * message from. + * @buffer: A pointer to the buffer in which to generate the + * message. + * @size: The size, in bytes, of space available in buffer. + * + * This routine generates to the provided buffer the portion of the + * driver-unique report message associated with the ECCESS[BKNER] + * field of the specified ECC status. + * + * Returns the number of characters generated on success; otherwise, < + * 0 on error. + */ +static int +ppc4xx_edac_generate_bank_message(const struct mem_ctl_info *mci, + const struct ppc4xx_ecc_status *status, + char *buffer, + size_t size) +{ + int n, total = 0; + unsigned int row, rows; + + n = snprintf(buffer, size, "%s: Banks: ", mci->dev_name); + + if (n < 0 || n >= size) + goto fail; + + buffer += n; + size -= n; + total += n; + + for (rows = 0, row = 0; row < mci->nr_csrows; row++) { + if (ppc4xx_edac_check_bank_error(status, row)) { + n = snprintf(buffer, size, "%s%u", + (rows++ ? ", " : ""), row); + + if (n < 0 || n >= size) + goto fail; + + buffer += n; + size -= n; + total += n; + } + } + + n = snprintf(buffer, size, "%s; ", rows ? "" : "None"); + + if (n < 0 || n >= size) + goto fail; + + buffer += n; + size -= n; + total += n; + + fail: + return total; +} + +/** + * ppc4xx_edac_generate_checkbit_message - generate interpretted checkbit message + * @mci: A pointer to the EDAC memory controller instance associated + * with the checkbit message being generated. + * @status: A pointer to the ECC status structure to generate the + * message from. + * @buffer: A pointer to the buffer in which to generate the + * message. + * @size: The size, in bytes, of space available in buffer. + * + * This routine generates to the provided buffer the portion of the + * driver-unique report message associated with the ECCESS[CKBER] + * field of the specified ECC status. + * + * Returns the number of characters generated on success; otherwise, < + * 0 on error. + */ +static int +ppc4xx_edac_generate_checkbit_message(const struct mem_ctl_info *mci, + const struct ppc4xx_ecc_status *status, + char *buffer, + size_t size) +{ + const struct ppc4xx_edac_pdata *pdata = mci->pvt_info; + const char *ckber = NULL; + + switch (status->ecces & SDRAM_ECCES_CKBER_MASK) { + case SDRAM_ECCES_CKBER_NONE: + ckber = "None"; + break; + case SDRAM_ECCES_CKBER_32_ECC_0_3: + ckber = "ECC0:3"; + break; + case SDRAM_ECCES_CKBER_32_ECC_4_8: + switch (mfsdram(&pdata->dcr_host, SDRAM_MCOPT1) & + SDRAM_MCOPT1_WDTH_MASK) { + case SDRAM_MCOPT1_WDTH_16: + ckber = "ECC0:3"; + break; + case SDRAM_MCOPT1_WDTH_32: + ckber = "ECC4:8"; + break; + default: + ckber = "Unknown"; + break; + } + break; + case SDRAM_ECCES_CKBER_32_ECC_0_8: + ckber = "ECC0:8"; + break; + default: + ckber = "Unknown"; + break; + } + + return snprintf(buffer, size, "Checkbit Error: %s", ckber); +} + +/** + * ppc4xx_edac_generate_lane_message - generate interpretted byte lane message + * @mci: A pointer to the EDAC memory controller instance associated + * with the byte lane message being generated. + * @status: A pointer to the ECC status structure to generate the + * message from. + * @buffer: A pointer to the buffer in which to generate the + * message. + * @size: The size, in bytes, of space available in buffer. + * + * This routine generates to the provided buffer the portion of the + * driver-unique report message associated with the ECCESS[BNCE] + * field of the specified ECC status. + * + * Returns the number of characters generated on success; otherwise, < + * 0 on error. + */ +static int +ppc4xx_edac_generate_lane_message(const struct mem_ctl_info *mci, + const struct ppc4xx_ecc_status *status, + char *buffer, + size_t size) +{ + int n, total = 0; + unsigned int lane, lanes; + const unsigned int first_lane = 0; + const unsigned int lane_count = 16; + + n = snprintf(buffer, size, "; Byte Lane Errors: "); + + if (n < 0 || n >= size) + goto fail; + + buffer += n; + size -= n; + total += n; + + for (lanes = 0, lane = first_lane; lane < lane_count; lane++) { + if ((status->ecces & SDRAM_ECCES_BNCE_ENCODE(lane)) != 0) { + n = snprintf(buffer, size, + "%s%u", + (lanes++ ? ", " : ""), lane); + + if (n < 0 || n >= size) + goto fail; + + buffer += n; + size -= n; + total += n; + } + } + + n = snprintf(buffer, size, "%s; ", lanes ? "" : "None"); + + if (n < 0 || n >= size) + goto fail; + + buffer += n; + size -= n; + total += n; + + fail: + return total; +} + +/** + * ppc4xx_edac_generate_ecc_message - generate interpretted ECC status message + * @mci: A pointer to the EDAC memory controller instance associated + * with the ECCES message being generated. + * @status: A pointer to the ECC status structure to generate the + * message from. + * @buffer: A pointer to the buffer in which to generate the + * message. + * @size: The size, in bytes, of space available in buffer. + * + * This routine generates to the provided buffer the portion of the + * driver-unique report message associated with the ECCESS register of + * the specified ECC status. + * + * Returns the number of characters generated on success; otherwise, < + * 0 on error. + */ +static int +ppc4xx_edac_generate_ecc_message(const struct mem_ctl_info *mci, + const struct ppc4xx_ecc_status *status, + char *buffer, + size_t size) +{ + int n, total = 0; + + n = ppc4xx_edac_generate_bank_message(mci, status, buffer, size); + + if (n < 0 || n >= size) + goto fail; + + buffer += n; + size -= n; + total += n; + + n = ppc4xx_edac_generate_checkbit_message(mci, status, buffer, size); + + if (n < 0 || n >= size) + goto fail; + + buffer += n; + size -= n; + total += n; + + n = ppc4xx_edac_generate_lane_message(mci, status, buffer, size); + + if (n < 0 || n >= size) + goto fail; + + buffer += n; + size -= n; + total += n; + + fail: + return total; +} + +/** + * ppc4xx_edac_generate_plb_message - generate interpretted PLB status message + * @mci: A pointer to the EDAC memory controller instance associated + * with the PLB message being generated. + * @status: A pointer to the ECC status structure to generate the + * message from. + * @buffer: A pointer to the buffer in which to generate the + * message. + * @size: The size, in bytes, of space available in buffer. + * + * This routine generates to the provided buffer the portion of the + * driver-unique report message associated with the PLB-related BESR + * and/or WMIRQ registers of the specified ECC status. + * + * Returns the number of characters generated on success; otherwise, < + * 0 on error. + */ +static int +ppc4xx_edac_generate_plb_message(const struct mem_ctl_info *mci, + const struct ppc4xx_ecc_status *status, + char *buffer, + size_t size) +{ + unsigned int master; + bool read; + + if ((status->besr & SDRAM_BESR_MASK) == 0) + return 0; + + if ((status->besr & SDRAM_BESR_M0ET_MASK) == SDRAM_BESR_M0ET_NONE) + return 0; + + read = ((status->besr & SDRAM_BESR_M0RW_MASK) == SDRAM_BESR_M0RW_READ); + + master = SDRAM_BESR_M0ID_DECODE(status->besr); + + return snprintf(buffer, size, + "%s error w/ PLB master %u \"%s\"; ", + (read ? "Read" : "Write"), + master, + (((master >= SDRAM_PLB_M0ID_FIRST) && + (master <= SDRAM_PLB_M0ID_LAST)) ? + ppc4xx_plb_masters[master] : "UNKNOWN")); +} + +/** + * ppc4xx_edac_generate_message - generate interpretted status message + * @mci: A pointer to the EDAC memory controller instance associated + * with the driver-unique message being generated. + * @status: A pointer to the ECC status structure to generate the + * message from. + * @buffer: A pointer to the buffer in which to generate the + * message. + * @size: The size, in bytes, of space available in buffer. + * + * This routine generates to the provided buffer the driver-unique + * EDAC report message from the specified ECC status. + */ +static void +ppc4xx_edac_generate_message(const struct mem_ctl_info *mci, + const struct ppc4xx_ecc_status *status, + char *buffer, + size_t size) +{ + int n; + + if (buffer == NULL || size == 0) + return; + + n = ppc4xx_edac_generate_ecc_message(mci, status, buffer, size); + + if (n < 0 || n >= size) + return; + + buffer += n; + size -= n; + + ppc4xx_edac_generate_plb_message(mci, status, buffer, size); +} + +#ifdef DEBUG +/** + * ppc4xx_ecc_dump_status - dump controller ECC status registers + * @mci: A pointer to the EDAC memory controller instance + * associated with the status being dumped. + * @status: A pointer to the ECC status structure to generate the + * dump from. + * + * This routine dumps to the kernel log buffer the raw and + * interpretted specified ECC status. + */ +static void +ppc4xx_ecc_dump_status(const struct mem_ctl_info *mci, + const struct ppc4xx_ecc_status *status) +{ + char message[PPC4XX_EDAC_MESSAGE_SIZE]; + + ppc4xx_edac_generate_message(mci, status, message, sizeof(message)); + + ppc4xx_edac_mc_printk(KERN_INFO, mci, + "\n" + "\tECCES: 0x%08x\n" + "\tWMIRQ: 0x%08x\n" + "\tBESR: 0x%08x\n" + "\tBEAR: 0x%08x%08x\n" + "\t%s\n", + status->ecces, + status->wmirq, + status->besr, + status->bearh, + status->bearl, + message); +} +#endif /* DEBUG */ + +/** + * ppc4xx_ecc_get_status - get controller ECC status + * @mci: A pointer to the EDAC memory controller instance + * associated with the status being retrieved. + * @status: A pointer to the ECC status structure to populate the + * ECC status with. + * + * This routine reads and masks, as appropriate, all the relevant + * status registers that deal with ibm,sdram-4xx-ddr2 ECC errors. + * While we read all of them, for correctable errors, we only expect + * to deal with ECCES. For uncorrectable errors, we expect to deal + * with all of them. + */ +static void +ppc4xx_ecc_get_status(const struct mem_ctl_info *mci, + struct ppc4xx_ecc_status *status) +{ + const struct ppc4xx_edac_pdata *pdata = mci->pvt_info; + const dcr_host_t *dcr_host = &pdata->dcr_host; + + status->ecces = mfsdram(dcr_host, SDRAM_ECCES) & SDRAM_ECCES_MASK; + status->wmirq = mfsdram(dcr_host, SDRAM_WMIRQ) & SDRAM_WMIRQ_MASK; + status->besr = mfsdram(dcr_host, SDRAM_BESR) & SDRAM_BESR_MASK; + status->bearl = mfsdram(dcr_host, SDRAM_BEARL); + status->bearh = mfsdram(dcr_host, SDRAM_BEARH); +} + +/** + * ppc4xx_ecc_clear_status - clear controller ECC status + * @mci: A pointer to the EDAC memory controller instance + * associated with the status being cleared. + * @status: A pointer to the ECC status structure containing the + * values to write to clear the ECC status. + * + * This routine clears--by writing the masked (as appropriate) status + * values back to--the status registers that deal with + * ibm,sdram-4xx-ddr2 ECC errors. + */ +static void +ppc4xx_ecc_clear_status(const struct mem_ctl_info *mci, + const struct ppc4xx_ecc_status *status) +{ + const struct ppc4xx_edac_pdata *pdata = mci->pvt_info; + const dcr_host_t *dcr_host = &pdata->dcr_host; + + mtsdram(dcr_host, SDRAM_ECCES, status->ecces & SDRAM_ECCES_MASK); + mtsdram(dcr_host, SDRAM_WMIRQ, status->wmirq & SDRAM_WMIRQ_MASK); + mtsdram(dcr_host, SDRAM_BESR, status->besr & SDRAM_BESR_MASK); + mtsdram(dcr_host, SDRAM_BEARL, 0); + mtsdram(dcr_host, SDRAM_BEARH, 0); +} + +/** + * ppc4xx_edac_handle_ce - handle controller correctable ECC error (CE) + * @mci: A pointer to the EDAC memory controller instance + * associated with the correctable error being handled and reported. + * @status: A pointer to the ECC status structure associated with + * the correctable error being handled and reported. + * + * This routine handles an ibm,sdram-4xx-ddr2 controller ECC + * correctable error. Per the aforementioned discussion, there's not + * enough status available to use the full EDAC correctable error + * interface, so we just pass driver-unique message to the "no info" + * interface. + */ +static void +ppc4xx_edac_handle_ce(struct mem_ctl_info *mci, + const struct ppc4xx_ecc_status *status) +{ + int row; + char message[PPC4XX_EDAC_MESSAGE_SIZE]; + + ppc4xx_edac_generate_message(mci, status, message, sizeof(message)); + + for (row = 0; row < mci->nr_csrows; row++) + if (ppc4xx_edac_check_bank_error(status, row)) + edac_mc_handle_ce_no_info(mci, message); +} + +/** + * ppc4xx_edac_handle_ue - handle controller uncorrectable ECC error (UE) + * @mci: A pointer to the EDAC memory controller instance + * associated with the uncorrectable error being handled and + * reported. + * @status: A pointer to the ECC status structure associated with + * the uncorrectable error being handled and reported. + * + * This routine handles an ibm,sdram-4xx-ddr2 controller ECC + * uncorrectable error. + */ +static void +ppc4xx_edac_handle_ue(struct mem_ctl_info *mci, + const struct ppc4xx_ecc_status *status) +{ + const u64 bear = ((u64)status->bearh << 32 | status->bearl); + const unsigned long page = bear >> PAGE_SHIFT; + const unsigned long offset = bear & ~PAGE_MASK; + int row; + char message[PPC4XX_EDAC_MESSAGE_SIZE]; + + ppc4xx_edac_generate_message(mci, status, message, sizeof(message)); + + for (row = 0; row < mci->nr_csrows; row++) + if (ppc4xx_edac_check_bank_error(status, row)) + edac_mc_handle_ue(mci, page, offset, row, message); +} + +/** + * ppc4xx_edac_check - check controller for ECC errors + * @mci: A pointer to the EDAC memory controller instance + * associated with the ibm,sdram-4xx-ddr2 controller being + * checked. + * + * This routine is used to check and post ECC errors and is called by + * both the EDAC polling thread and this driver's CE and UE interrupt + * handler. + */ +static void +ppc4xx_edac_check(struct mem_ctl_info *mci) +{ +#ifdef DEBUG + static unsigned int count; +#endif + struct ppc4xx_ecc_status status; + + ppc4xx_ecc_get_status(mci, &status); + +#ifdef DEBUG + if (count++ % 30 == 0) + ppc4xx_ecc_dump_status(mci, &status); +#endif + + if (status.ecces & SDRAM_ECCES_UE) + ppc4xx_edac_handle_ue(mci, &status); + + if (status.ecces & SDRAM_ECCES_CE) + ppc4xx_edac_handle_ce(mci, &status); + + ppc4xx_ecc_clear_status(mci, &status); +} + +/** + * ppc4xx_edac_isr - SEC (CE) and DED (UE) interrupt service routine + * @irq: The virtual interrupt number being serviced. + * @dev_id: A pointer to the EDAC memory controller instance + * associated with the interrupt being handled. + * + * This routine implements the interrupt handler for both correctable + * (CE) and uncorrectable (UE) ECC errors for the ibm,sdram-4xx-ddr2 + * controller. It simply calls through to the same routine used during + * polling to check, report and clear the ECC status. + * + * Unconditionally returns IRQ_HANDLED. + */ +static irqreturn_t +ppc4xx_edac_isr(int irq, void *dev_id) +{ + struct mem_ctl_info *mci = dev_id; + + ppc4xx_edac_check(mci); + + return IRQ_HANDLED; +} + +/** + * ppc4xx_edac_get_dtype - return the controller memory width + * @mcopt1: The 32-bit Memory Controller Option 1 register value + * currently set for the controller, from which the width + * is derived. + * + * This routine returns the EDAC device type width appropriate for the + * current controller configuration. + * + * TODO: This needs to be conditioned dynamically through feature + * flags or some such when other controller variants are supported as + * the 405EX[r] is 16-/32-bit and the others are 32-/64-bit with the + * 16- and 64-bit field definition/value/enumeration (b1) overloaded + * among them. + * + * Returns a device type width enumeration. + */ +static enum dev_type __devinit +ppc4xx_edac_get_dtype(u32 mcopt1) +{ + switch (mcopt1 & SDRAM_MCOPT1_WDTH_MASK) { + case SDRAM_MCOPT1_WDTH_16: + return DEV_X2; + case SDRAM_MCOPT1_WDTH_32: + return DEV_X4; + default: + return DEV_UNKNOWN; + } +} + +/** + * ppc4xx_edac_get_mtype - return controller memory type + * @mcopt1: The 32-bit Memory Controller Option 1 register value + * currently set for the controller, from which the memory type + * is derived. + * + * This routine returns the EDAC memory type appropriate for the + * current controller configuration. + * + * Returns a memory type enumeration. + */ +static enum mem_type __devinit +ppc4xx_edac_get_mtype(u32 mcopt1) +{ + bool rden = ((mcopt1 & SDRAM_MCOPT1_RDEN_MASK) == SDRAM_MCOPT1_RDEN); + + switch (mcopt1 & SDRAM_MCOPT1_DDR_TYPE_MASK) { + case SDRAM_MCOPT1_DDR2_TYPE: + return rden ? MEM_RDDR2 : MEM_DDR2; + case SDRAM_MCOPT1_DDR1_TYPE: + return rden ? MEM_RDDR : MEM_DDR; + default: + return MEM_UNKNOWN; + } +} + +/** + * ppc4xx_edac_init_csrows - intialize driver instance rows + * @mci: A pointer to the EDAC memory controller instance + * associated with the ibm,sdram-4xx-ddr2 controller for which + * the csrows (i.e. banks/ranks) are being initialized. + * @mcopt1: The 32-bit Memory Controller Option 1 register value + * currently set for the controller, from which bank width + * and memory typ information is derived. + * + * This routine intializes the virtual "chip select rows" associated + * with the EDAC memory controller instance. An ibm,sdram-4xx-ddr2 + * controller bank/rank is mapped to a row. + * + * Returns 0 if OK; otherwise, -EINVAL if the memory bank size + * configuration cannot be determined. + */ +static int __devinit +ppc4xx_edac_init_csrows(struct mem_ctl_info *mci, u32 mcopt1) +{ + const struct ppc4xx_edac_pdata *pdata = mci->pvt_info; + int status = 0; + enum mem_type mtype; + enum dev_type dtype; + enum edac_type edac_mode; + int row; + u32 mbxcf, size; + static u32 ppc4xx_last_page; + + /* Establish the memory type and width */ + + mtype = ppc4xx_edac_get_mtype(mcopt1); + dtype = ppc4xx_edac_get_dtype(mcopt1); + + /* Establish EDAC mode */ + + if (mci->edac_cap & EDAC_FLAG_SECDED) + edac_mode = EDAC_SECDED; + else if (mci->edac_cap & EDAC_FLAG_EC) + edac_mode = EDAC_EC; + else + edac_mode = EDAC_NONE; + + /* + * Initialize each chip select row structure which correspond + * 1:1 with a controller bank/rank. + */ + + for (row = 0; row < mci->nr_csrows; row++) { + struct csrow_info *csi = &mci->csrows[row]; + + /* + * Get the configuration settings for this + * row/bank/rank and skip disabled banks. + */ + + mbxcf = mfsdram(&pdata->dcr_host, SDRAM_MBXCF(row)); + + if ((mbxcf & SDRAM_MBCF_BE_MASK) != SDRAM_MBCF_BE_ENABLE) + continue; + + /* Map the bank configuration size setting to pages. */ + + size = mbxcf & SDRAM_MBCF_SZ_MASK; + + switch (size) { + case SDRAM_MBCF_SZ_4MB: + case SDRAM_MBCF_SZ_8MB: + case SDRAM_MBCF_SZ_16MB: + case SDRAM_MBCF_SZ_32MB: + case SDRAM_MBCF_SZ_64MB: + case SDRAM_MBCF_SZ_128MB: + case SDRAM_MBCF_SZ_256MB: + case SDRAM_MBCF_SZ_512MB: + case SDRAM_MBCF_SZ_1GB: + case SDRAM_MBCF_SZ_2GB: + case SDRAM_MBCF_SZ_4GB: + case SDRAM_MBCF_SZ_8GB: + csi->nr_pages = SDRAM_MBCF_SZ_TO_PAGES(size); + break; + default: + ppc4xx_edac_mc_printk(KERN_ERR, mci, + "Unrecognized memory bank %d " + "size 0x%08x\n", + row, SDRAM_MBCF_SZ_DECODE(size)); + status = -EINVAL; + goto done; + } + + csi->first_page = ppc4xx_last_page; + csi->last_page = csi->first_page + csi->nr_pages - 1; + csi->page_mask = 0; + + /* + * It's unclear exactly what grain should be set to + * here. The SDRAM_ECCES register allows resolution of + * an error down to a nibble which would potentially + * argue for a grain of '1' byte, even though we only + * know the associated address for uncorrectable + * errors. This value is not used at present for + * anything other than error reporting so getting it + * wrong should be of little consequence. Other + * possible values would be the PLB width (16), the + * page size (PAGE_SIZE) or the memory width (2 or 4). + */ + + csi->grain = 1; + + csi->mtype = mtype; + csi->dtype = dtype; + + csi->edac_mode = edac_mode; + + ppc4xx_last_page += csi->nr_pages; + } + + done: + return status; +} + +/** + * ppc4xx_edac_mc_init - intialize driver instance + * @mci: A pointer to the EDAC memory controller instance being + * initialized. + * @op: A pointer to the OpenFirmware device tree node associated + * with the controller this EDAC instance is bound to. + * @match: A pointer to the OpenFirmware device tree match + * information associated with the controller this EDAC instance + * is bound to. + * @dcr_host: A pointer to the DCR data containing the DCR mapping + * for this controller instance. + * @mcopt1: The 32-bit Memory Controller Option 1 register value + * currently set for the controller, from which ECC capabilities + * and scrub mode are derived. + * + * This routine performs initialization of the EDAC memory controller + * instance and related driver-private data associated with the + * ibm,sdram-4xx-ddr2 memory controller the instance is bound to. + * + * Returns 0 if OK; otherwise, < 0 on error. + */ +static int __devinit +ppc4xx_edac_mc_init(struct mem_ctl_info *mci, + struct of_device *op, + const struct of_device_id *match, + const dcr_host_t *dcr_host, + u32 mcopt1) +{ + int status = 0; + const u32 memcheck = (mcopt1 & SDRAM_MCOPT1_MCHK_MASK); + struct ppc4xx_edac_pdata *pdata = NULL; + const struct device_node *np = op->node; + + if (match == NULL) + return -EINVAL; + + /* Initial driver pointers and private data */ + + mci->dev = &op->dev; + + dev_set_drvdata(mci->dev, mci); + + pdata = mci->pvt_info; + + pdata->dcr_host = *dcr_host; + pdata->irqs.sec = NO_IRQ; + pdata->irqs.ded = NO_IRQ; + + /* Initialize controller capabilities and configuration */ + + mci->mtype_cap = (MEM_FLAG_DDR | MEM_FLAG_RDDR | + MEM_FLAG_DDR2 | MEM_FLAG_RDDR2); + + mci->edac_ctl_cap = (EDAC_FLAG_NONE | + EDAC_FLAG_EC | + EDAC_FLAG_SECDED); + + mci->scrub_cap = SCRUB_NONE; + mci->scrub_mode = SCRUB_NONE; + + /* + * Update the actual capabilites based on the MCOPT1[MCHK] + * settings. Scrubbing is only useful if reporting is enabled. + */ + + switch (memcheck) { + case SDRAM_MCOPT1_MCHK_CHK: + mci->edac_cap = EDAC_FLAG_EC; + break; + case SDRAM_MCOPT1_MCHK_CHK_REP: + mci->edac_cap = (EDAC_FLAG_EC | EDAC_FLAG_SECDED); + mci->scrub_mode = SCRUB_SW_SRC; + break; + default: + mci->edac_cap = EDAC_FLAG_NONE; + break; + } + + /* Initialize strings */ + + mci->mod_name = PPC4XX_EDAC_MODULE_NAME; + mci->mod_ver = PPC4XX_EDAC_MODULE_REVISION; + mci->ctl_name = match->compatible, + mci->dev_name = np->full_name; + + /* Initialize callbacks */ + + mci->edac_check = ppc4xx_edac_check; + mci->ctl_page_to_phys = NULL; + + /* Initialize chip select rows */ + + status = ppc4xx_edac_init_csrows(mci, mcopt1); + + if (status) + ppc4xx_edac_mc_printk(KERN_ERR, mci, + "Failed to initialize rows!\n"); + + return status; +} + +/** + * ppc4xx_edac_register_irq - setup and register controller interrupts + * @op: A pointer to the OpenFirmware device tree node associated + * with the controller this EDAC instance is bound to. + * @mci: A pointer to the EDAC memory controller instance + * associated with the ibm,sdram-4xx-ddr2 controller for which + * interrupts are being registered. + * + * This routine parses the correctable (CE) and uncorrectable error (UE) + * interrupts from the device tree node and maps and assigns them to + * the associated EDAC memory controller instance. + * + * Returns 0 if OK; otherwise, -ENODEV if the interrupts could not be + * mapped and assigned. + */ +static int __devinit +ppc4xx_edac_register_irq(struct of_device *op, struct mem_ctl_info *mci) +{ + int status = 0; + int ded_irq, sec_irq; + struct ppc4xx_edac_pdata *pdata = mci->pvt_info; + struct device_node *np = op->node; + + ded_irq = irq_of_parse_and_map(np, INTMAP_ECCDED_INDEX); + sec_irq = irq_of_parse_and_map(np, INTMAP_ECCSEC_INDEX); + + if (ded_irq == NO_IRQ || sec_irq == NO_IRQ) { + ppc4xx_edac_mc_printk(KERN_ERR, mci, + "Unable to map interrupts.\n"); + status = -ENODEV; + goto fail; + } + + status = request_irq(ded_irq, + ppc4xx_edac_isr, + IRQF_DISABLED, + "[EDAC] MC ECCDED", + mci); + + if (status < 0) { + ppc4xx_edac_mc_printk(KERN_ERR, mci, + "Unable to request irq %d for ECC DED", + ded_irq); + status = -ENODEV; + goto fail1; + } + + status = request_irq(sec_irq, + ppc4xx_edac_isr, + IRQF_DISABLED, + "[EDAC] MC ECCSEC", + mci); + + if (status < 0) { + ppc4xx_edac_mc_printk(KERN_ERR, mci, + "Unable to request irq %d for ECC SEC", + sec_irq); + status = -ENODEV; + goto fail2; + } + + ppc4xx_edac_mc_printk(KERN_INFO, mci, "ECCDED irq is %d\n", ded_irq); + ppc4xx_edac_mc_printk(KERN_INFO, mci, "ECCSEC irq is %d\n", sec_irq); + + pdata->irqs.ded = ded_irq; + pdata->irqs.sec = sec_irq; + + return 0; + + fail2: + free_irq(sec_irq, mci); + + fail1: + free_irq(ded_irq, mci); + + fail: + return status; +} + +/** + * ppc4xx_edac_map_dcrs - locate and map controller registers + * @np: A pointer to the device tree node containing the DCR + * resources to map. + * @dcr_host: A pointer to the DCR data to populate with the + * DCR mapping. + * + * This routine attempts to locate in the device tree and map the DCR + * register resources associated with the controller's indirect DCR + * address and data windows. + * + * Returns 0 if the DCRs were successfully mapped; otherwise, < 0 on + * error. + */ +static int __devinit +ppc4xx_edac_map_dcrs(const struct device_node *np, dcr_host_t *dcr_host) +{ + unsigned int dcr_base, dcr_len; + + if (np == NULL || dcr_host == NULL) + return -EINVAL; + + /* Get the DCR resource extent and sanity check the values. */ + + dcr_base = dcr_resource_start(np, 0); + dcr_len = dcr_resource_len(np, 0); + + if (dcr_base == 0 || dcr_len == 0) { + ppc4xx_edac_printk(KERN_ERR, + "Failed to obtain DCR property.\n"); + return -ENODEV; + } + + if (dcr_len != SDRAM_DCR_RESOURCE_LEN) { + ppc4xx_edac_printk(KERN_ERR, + "Unexpected DCR length %d, expected %d.\n", + dcr_len, SDRAM_DCR_RESOURCE_LEN); + return -ENODEV; + } + + /* Attempt to map the DCR extent. */ + + *dcr_host = dcr_map(np, dcr_base, dcr_len); + + if (!DCR_MAP_OK(*dcr_host)) { + ppc4xx_edac_printk(KERN_INFO, "Failed to map DCRs.\n"); + return -ENODEV; + } + + return 0; +} + +/** + * ppc4xx_edac_probe - check controller and bind driver + * @op: A pointer to the OpenFirmware device tree node associated + * with the controller being probed for driver binding. + * @match: A pointer to the OpenFirmware device tree match + * information associated with the controller being probed + * for driver binding. + * + * This routine probes a specific ibm,sdram-4xx-ddr2 controller + * instance for binding with the driver. + * + * Returns 0 if the controller instance was successfully bound to the + * driver; otherwise, < 0 on error. + */ +static int __devinit +ppc4xx_edac_probe(struct of_device *op, const struct of_device_id *match) +{ + int status = 0; + u32 mcopt1, memcheck; + dcr_host_t dcr_host; + const struct device_node *np = op->node; + struct mem_ctl_info *mci = NULL; + static int ppc4xx_edac_instance; + + /* + * At this point, we only support the controller realized on + * the AMCC PPC 405EX[r]. Reject anything else. + */ + + if (!of_device_is_compatible(np, "ibm,sdram-405ex") && + !of_device_is_compatible(np, "ibm,sdram-405exr")) { + ppc4xx_edac_printk(KERN_NOTICE, + "Only the PPC405EX[r] is supported.\n"); + return -ENODEV; + } + + /* + * Next, get the DCR property and attempt to map it so that we + * can probe the controller. + */ + + status = ppc4xx_edac_map_dcrs(np, &dcr_host); + + if (status) + return status; + + /* + * First determine whether ECC is enabled at all. If not, + * there is no useful checking or monitoring that can be done + * for this controller. + */ + + mcopt1 = mfsdram(&dcr_host, SDRAM_MCOPT1); + memcheck = (mcopt1 & SDRAM_MCOPT1_MCHK_MASK); + + if (memcheck == SDRAM_MCOPT1_MCHK_NON) { + ppc4xx_edac_printk(KERN_INFO, "%s: No ECC memory detected or " + "ECC is disabled.\n", np->full_name); + status = -ENODEV; + goto done; + } + + /* + * At this point, we know ECC is enabled, allocate an EDAC + * controller instance and perform the appropriate + * initialization. + */ + + mci = edac_mc_alloc(sizeof(struct ppc4xx_edac_pdata), + ppc4xx_edac_nr_csrows, + ppc4xx_edac_nr_chans, + ppc4xx_edac_instance); + + if (mci == NULL) { + ppc4xx_edac_printk(KERN_ERR, "%s: " + "Failed to allocate EDAC MC instance!\n", + np->full_name); + status = -ENOMEM; + goto done; + } + + status = ppc4xx_edac_mc_init(mci, op, match, &dcr_host, mcopt1); + + if (status) { + ppc4xx_edac_mc_printk(KERN_ERR, mci, + "Failed to initialize instance!\n"); + goto fail; + } + + /* + * We have a valid, initialized EDAC instance bound to the + * controller. Attempt to register it with the EDAC subsystem + * and, if necessary, register interrupts. + */ + + if (edac_mc_add_mc(mci)) { + ppc4xx_edac_mc_printk(KERN_ERR, mci, + "Failed to add instance!\n"); + status = -ENODEV; + goto fail; + } + + if (edac_op_state == EDAC_OPSTATE_INT) { + status = ppc4xx_edac_register_irq(op, mci); + + if (status) + goto fail1; + } + + ppc4xx_edac_instance++; + + return 0; + + fail1: + edac_mc_del_mc(mci->dev); + + fail: + edac_mc_free(mci); + + done: + return status; +} + +/** + * ppc4xx_edac_remove - unbind driver from controller + * @op: A pointer to the OpenFirmware device tree node associated + * with the controller this EDAC instance is to be unbound/removed + * from. + * + * This routine unbinds the EDAC memory controller instance associated + * with the specified ibm,sdram-4xx-ddr2 controller described by the + * OpenFirmware device tree node passed as a parameter. + * + * Unconditionally returns 0. + */ +static int +ppc4xx_edac_remove(struct of_device *op) +{ + struct mem_ctl_info *mci = dev_get_drvdata(&op->dev); + struct ppc4xx_edac_pdata *pdata = mci->pvt_info; + + if (edac_op_state == EDAC_OPSTATE_INT) { + free_irq(pdata->irqs.sec, mci); + free_irq(pdata->irqs.ded, mci); + } + + dcr_unmap(pdata->dcr_host, SDRAM_DCR_RESOURCE_LEN); + + edac_mc_del_mc(mci->dev); + edac_mc_free(mci); + + return 0; +} + +/** + * ppc4xx_edac_opstate_init - initialize EDAC reporting method + * + * This routine ensures that the EDAC memory controller reporting + * method is mapped to a sane value as the EDAC core defines the value + * to EDAC_OPSTATE_INVAL by default. We don't call the global + * opstate_init as that defaults to polling and we want interrupt as + * the default. + */ +static inline void __init +ppc4xx_edac_opstate_init(void) +{ + switch (edac_op_state) { + case EDAC_OPSTATE_POLL: + case EDAC_OPSTATE_INT: + break; + default: + edac_op_state = EDAC_OPSTATE_INT; + break; + } + + ppc4xx_edac_printk(KERN_INFO, "Reporting type: %s\n", + ((edac_op_state == EDAC_OPSTATE_POLL) ? + EDAC_OPSTATE_POLL_STR : + ((edac_op_state == EDAC_OPSTATE_INT) ? + EDAC_OPSTATE_INT_STR : + EDAC_OPSTATE_UNKNOWN_STR))); +} + +/** + * ppc4xx_edac_init - driver/module insertion entry point + * + * This routine is the driver/module insertion entry point. It + * initializes the EDAC memory controller reporting state and + * registers the driver as an OpenFirmware device tree platform + * driver. + */ +static int __init +ppc4xx_edac_init(void) +{ + ppc4xx_edac_printk(KERN_INFO, PPC4XX_EDAC_MODULE_REVISION "\n"); + + ppc4xx_edac_opstate_init(); + + return of_register_platform_driver(&ppc4xx_edac_driver); +} + +/** + * ppc4xx_edac_exit - driver/module removal entry point + * + * This routine is the driver/module removal entry point. It + * unregisters the driver as an OpenFirmware device tree platform + * driver. + */ +static void __exit +ppc4xx_edac_exit(void) +{ + of_unregister_platform_driver(&ppc4xx_edac_driver); +} + +module_init(ppc4xx_edac_init); +module_exit(ppc4xx_edac_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Grant Erickson <gerickson@nuovations.com>"); +MODULE_DESCRIPTION("EDAC MC Driver for the PPC4xx IBM DDR2 Memory Controller"); +module_param(edac_op_state, int, 0444); +MODULE_PARM_DESC(edac_op_state, "EDAC Error Reporting State: " + "0=" EDAC_OPSTATE_POLL_STR ", 2=" EDAC_OPSTATE_INT_STR); diff --git a/drivers/edac/ppc4xx_edac.h b/drivers/edac/ppc4xx_edac.h new file mode 100644 index 000000000000..d3154764c449 --- /dev/null +++ b/drivers/edac/ppc4xx_edac.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2008 Nuovation System Designs, LLC + * Grant Erickson <gerickson@nuovations.com> + * + * This file defines processor mnemonics for accessing and managing + * the IBM DDR1/DDR2 ECC controller found in the 405EX[r], 440SP, + * 440SPe, 460EX, 460GT and 460SX. + * + * 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; version 2 of the + * License. + * + */ + +#ifndef __PPC4XX_EDAC_H +#define __PPC4XX_EDAC_H + +#include <linux/types.h> + +/* + * Macro for generating register field mnemonics + */ +#define PPC_REG_BITS 32 +#define PPC_REG_VAL(bit, val) ((val) << ((PPC_REG_BITS - 1) - (bit))) +#define PPC_REG_DECODE(bit, val) ((val) >> ((PPC_REG_BITS - 1) - (bit))) + +/* + * IBM 4xx DDR1/DDR2 SDRAM memory controller registers (at least those + * relevant to ECC) + */ +#define SDRAM_BESR 0x00 /* Error status (read/clear) */ +#define SDRAM_BESRT 0x01 /* Error statuss (test/set) */ +#define SDRAM_BEARL 0x02 /* Error address low */ +#define SDRAM_BEARH 0x03 /* Error address high */ +#define SDRAM_WMIRQ 0x06 /* Write master (read/clear) */ +#define SDRAM_WMIRQT 0x07 /* Write master (test/set) */ +#define SDRAM_MCOPT1 0x20 /* Controller options 1 */ +#define SDRAM_MBXCF_BASE 0x40 /* Bank n configuration base */ +#define SDRAM_MBXCF(n) (SDRAM_MBXCF_BASE + (4 * (n))) +#define SDRAM_MB0CF SDRAM_MBXCF(0) +#define SDRAM_MB1CF SDRAM_MBXCF(1) +#define SDRAM_MB2CF SDRAM_MBXCF(2) +#define SDRAM_MB3CF SDRAM_MBXCF(3) +#define SDRAM_ECCCR 0x98 /* ECC error status */ +#define SDRAM_ECCES SDRAM_ECCCR + +/* + * PLB Master IDs + */ +#define SDRAM_PLB_M0ID_FIRST 0 +#define SDRAM_PLB_M0ID_ICU SDRAM_PLB_M0ID_FIRST +#define SDRAM_PLB_M0ID_PCIE0 1 +#define SDRAM_PLB_M0ID_PCIE1 2 +#define SDRAM_PLB_M0ID_DMA 3 +#define SDRAM_PLB_M0ID_DCU 4 +#define SDRAM_PLB_M0ID_OPB 5 +#define SDRAM_PLB_M0ID_MAL 6 +#define SDRAM_PLB_M0ID_SEC 7 +#define SDRAM_PLB_M0ID_AHB 8 +#define SDRAM_PLB_M0ID_LAST SDRAM_PLB_M0ID_AHB +#define SDRAM_PLB_M0ID_COUNT (SDRAM_PLB_M0ID_LAST - \ + SDRAM_PLB_M0ID_FIRST + 1) + +/* + * Memory Controller Bus Error Status Register + */ +#define SDRAM_BESR_MASK PPC_REG_VAL(7, 0xFF) +#define SDRAM_BESR_M0ID_MASK PPC_REG_VAL(3, 0xF) +#define SDRAM_BESR_M0ID_DECODE(n) PPC_REG_DECODE(3, n) +#define SDRAM_BESR_M0ID_ICU PPC_REG_VAL(3, SDRAM_PLB_M0ID_ICU) +#define SDRAM_BESR_M0ID_PCIE0 PPC_REG_VAL(3, SDRAM_PLB_M0ID_PCIE0) +#define SDRAM_BESR_M0ID_PCIE1 PPC_REG_VAL(3, SDRAM_PLB_M0ID_PCIE1) +#define SDRAM_BESR_M0ID_DMA PPC_REG_VAL(3, SDRAM_PLB_M0ID_DMA) +#define SDRAM_BESR_M0ID_DCU PPC_REG_VAL(3, SDRAM_PLB_M0ID_DCU) +#define SDRAM_BESR_M0ID_OPB PPC_REG_VAL(3, SDRAM_PLB_M0ID_OPB) +#define SDRAM_BESR_M0ID_MAL PPC_REG_VAL(3, SDRAM_PLB_M0ID_MAL) +#define SDRAM_BESR_M0ID_SEC PPC_REG_VAL(3, SDRAM_PLB_M0ID_SEC) +#define SDRAM_BESR_M0ID_AHB PPC_REG_VAL(3, SDRAM_PLB_M0ID_AHB) +#define SDRAM_BESR_M0ET_MASK PPC_REG_VAL(6, 0x7) +#define SDRAM_BESR_M0ET_NONE PPC_REG_VAL(6, 0) +#define SDRAM_BESR_M0ET_ECC PPC_REG_VAL(6, 1) +#define SDRAM_BESR_M0RW_MASK PPC_REG_VAL(7, 1) +#define SDRAM_BESR_M0RW_WRITE PPC_REG_VAL(7, 0) +#define SDRAM_BESR_M0RW_READ PPC_REG_VAL(7, 1) + +/* + * Memory Controller PLB Write Master Interrupt Register + */ +#define SDRAM_WMIRQ_MASK PPC_REG_VAL(8, 0x1FF) +#define SDRAM_WMIRQ_ENCODE(id) PPC_REG_VAL((id % \ + SDRAM_PLB_M0ID_COUNT), 1) +#define SDRAM_WMIRQ_ICU PPC_REG_VAL(SDRAM_PLB_M0ID_ICU, 1) +#define SDRAM_WMIRQ_PCIE0 PPC_REG_VAL(SDRAM_PLB_M0ID_PCIE0, 1) +#define SDRAM_WMIRQ_PCIE1 PPC_REG_VAL(SDRAM_PLB_M0ID_PCIE1, 1) +#define SDRAM_WMIRQ_DMA PPC_REG_VAL(SDRAM_PLB_M0ID_DMA, 1) +#define SDRAM_WMIRQ_DCU PPC_REG_VAL(SDRAM_PLB_M0ID_DCU, 1) +#define SDRAM_WMIRQ_OPB PPC_REG_VAL(SDRAM_PLB_M0ID_OPB, 1) +#define SDRAM_WMIRQ_MAL PPC_REG_VAL(SDRAM_PLB_M0ID_MAL, 1) +#define SDRAM_WMIRQ_SEC PPC_REG_VAL(SDRAM_PLB_M0ID_SEC, 1) +#define SDRAM_WMIRQ_AHB PPC_REG_VAL(SDRAM_PLB_M0ID_AHB, 1) + +/* + * Memory Controller Options 1 Register + */ +#define SDRAM_MCOPT1_MCHK_MASK PPC_REG_VAL(3, 0x3) /* ECC mask */ +#define SDRAM_MCOPT1_MCHK_NON PPC_REG_VAL(3, 0x0) /* No ECC gen */ +#define SDRAM_MCOPT1_MCHK_GEN PPC_REG_VAL(3, 0x2) /* ECC gen */ +#define SDRAM_MCOPT1_MCHK_CHK PPC_REG_VAL(3, 0x1) /* ECC gen and chk */ +#define SDRAM_MCOPT1_MCHK_CHK_REP PPC_REG_VAL(3, 0x3) /* ECC gen/chk/rpt */ +#define SDRAM_MCOPT1_MCHK_DECODE(n) ((((u32)(n)) >> 28) & 0x3) +#define SDRAM_MCOPT1_RDEN_MASK PPC_REG_VAL(4, 0x1) /* Rgstrd DIMM mask */ +#define SDRAM_MCOPT1_RDEN PPC_REG_VAL(4, 0x1) /* Rgstrd DIMM enbl */ +#define SDRAM_MCOPT1_WDTH_MASK PPC_REG_VAL(7, 0x1) /* Width mask */ +#define SDRAM_MCOPT1_WDTH_32 PPC_REG_VAL(7, 0x0) /* 32 bits */ +#define SDRAM_MCOPT1_WDTH_16 PPC_REG_VAL(7, 0x1) /* 16 bits */ +#define SDRAM_MCOPT1_DDR_TYPE_MASK PPC_REG_VAL(11, 0x1) /* DDR type mask */ +#define SDRAM_MCOPT1_DDR1_TYPE PPC_REG_VAL(11, 0x0) /* DDR1 type */ +#define SDRAM_MCOPT1_DDR2_TYPE PPC_REG_VAL(11, 0x1) /* DDR2 type */ + +/* + * Memory Bank 0 - n Configuration Register + */ +#define SDRAM_MBCF_BA_MASK PPC_REG_VAL(12, 0x1FFF) +#define SDRAM_MBCF_SZ_MASK PPC_REG_VAL(19, 0xF) +#define SDRAM_MBCF_SZ_DECODE(mbxcf) PPC_REG_DECODE(19, mbxcf) +#define SDRAM_MBCF_SZ_4MB PPC_REG_VAL(19, 0x0) +#define SDRAM_MBCF_SZ_8MB PPC_REG_VAL(19, 0x1) +#define SDRAM_MBCF_SZ_16MB PPC_REG_VAL(19, 0x2) +#define SDRAM_MBCF_SZ_32MB PPC_REG_VAL(19, 0x3) +#define SDRAM_MBCF_SZ_64MB PPC_REG_VAL(19, 0x4) +#define SDRAM_MBCF_SZ_128MB PPC_REG_VAL(19, 0x5) +#define SDRAM_MBCF_SZ_256MB PPC_REG_VAL(19, 0x6) +#define SDRAM_MBCF_SZ_512MB PPC_REG_VAL(19, 0x7) +#define SDRAM_MBCF_SZ_1GB PPC_REG_VAL(19, 0x8) +#define SDRAM_MBCF_SZ_2GB PPC_REG_VAL(19, 0x9) +#define SDRAM_MBCF_SZ_4GB PPC_REG_VAL(19, 0xA) +#define SDRAM_MBCF_SZ_8GB PPC_REG_VAL(19, 0xB) +#define SDRAM_MBCF_AM_MASK PPC_REG_VAL(23, 0xF) +#define SDRAM_MBCF_AM_MODE0 PPC_REG_VAL(23, 0x0) +#define SDRAM_MBCF_AM_MODE1 PPC_REG_VAL(23, 0x1) +#define SDRAM_MBCF_AM_MODE2 PPC_REG_VAL(23, 0x2) +#define SDRAM_MBCF_AM_MODE3 PPC_REG_VAL(23, 0x3) +#define SDRAM_MBCF_AM_MODE4 PPC_REG_VAL(23, 0x4) +#define SDRAM_MBCF_AM_MODE5 PPC_REG_VAL(23, 0x5) +#define SDRAM_MBCF_AM_MODE6 PPC_REG_VAL(23, 0x6) +#define SDRAM_MBCF_AM_MODE7 PPC_REG_VAL(23, 0x7) +#define SDRAM_MBCF_AM_MODE8 PPC_REG_VAL(23, 0x8) +#define SDRAM_MBCF_AM_MODE9 PPC_REG_VAL(23, 0x9) +#define SDRAM_MBCF_BE_MASK PPC_REG_VAL(31, 0x1) +#define SDRAM_MBCF_BE_DISABLE PPC_REG_VAL(31, 0x0) +#define SDRAM_MBCF_BE_ENABLE PPC_REG_VAL(31, 0x1) + +/* + * ECC Error Status + */ +#define SDRAM_ECCES_MASK PPC_REG_VAL(21, 0x3FFFFF) +#define SDRAM_ECCES_BNCE_MASK PPC_REG_VAL(15, 0xFFFF) +#define SDRAM_ECCES_BNCE_ENCODE(lane) PPC_REG_VAL(((lane) & 0xF), 1) +#define SDRAM_ECCES_CKBER_MASK PPC_REG_VAL(17, 0x3) +#define SDRAM_ECCES_CKBER_NONE PPC_REG_VAL(17, 0) +#define SDRAM_ECCES_CKBER_16_ECC_0_3 PPC_REG_VAL(17, 2) +#define SDRAM_ECCES_CKBER_32_ECC_0_3 PPC_REG_VAL(17, 1) +#define SDRAM_ECCES_CKBER_32_ECC_4_8 PPC_REG_VAL(17, 2) +#define SDRAM_ECCES_CKBER_32_ECC_0_8 PPC_REG_VAL(17, 3) +#define SDRAM_ECCES_CE PPC_REG_VAL(18, 1) +#define SDRAM_ECCES_UE PPC_REG_VAL(19, 1) +#define SDRAM_ECCES_BKNER_MASK PPC_REG_VAL(21, 0x3) +#define SDRAM_ECCES_BK0ER PPC_REG_VAL(20, 1) +#define SDRAM_ECCES_BK1ER PPC_REG_VAL(21, 1) + +#endif /* __PPC4XX_EDAC_H */ |