* [v1 PATCH 0/4] Add INT mode support for EDAC drivers on Maple
From: Harry Ciao @ 2009-05-18 10:04 UTC (permalink / raw)
To: benh, bluesmoke-devel; +Cc: linuxppc-dev, linux-kernel
Hi Ben,
This is the v2 patches that have integrated your suggestions to
remove the refcount for a hwriq2virq mapping as long as we don't
dispose it, the changes are mostly within the 1/4 patch where the
unnecessary refcount and irqmap structure are removed, and callings
to edac_put_mpic_irq() are also removed from the rest of patches.
Since there are 3 EDAC modules on Maple will use one same copy of
code to create hwirq2virq mappings, I perfer to preserve it in
edac_mpic_irq.c.
How do feel about the assumption that MPIC will latch INT 0 pin for
the NMI Reqeust Messages whose vector is == 0? This is the thing that
I have least confidence in, so far all I can get is the brief
introduction in CPC925 user manual that "This interrupt vector is used
to set a corresponding interrupt latch", P111, so I think it imples that
vector==0 will latch pin #0.
Many thanks for all your comments!
Best regards,
Harry
^ permalink raw reply
* [v1 PATCH 1/4] EDAC: MPIC Hypertransport IRQ support
From: Harry Ciao @ 2009-05-18 10:04 UTC (permalink / raw)
To: benh, bluesmoke-devel; +Cc: linuxppc-dev, linux-kernel
In-Reply-To: <1242641100-15324-1-git-send-email-qingtao.cao@windriver.com>
Collect the machine specific code that creates the hwirq2virq mapping
for the possible multiple EDAC modules on the same machine that supports
MPIC.
Multiple calling of irq_create_of_mapping() for the same hwirq will
always return the same virq, since the mapping won't be refcounted so
we just don't call irq_dispose_mapping() against it. This won't occupy
unnecessary resource since irq_map[] is of fixed size(==512).
The edac_mpic_irq.c is inert for EDAC drivers where related hardware
is not connecting to MPIC, so it should be controlled by CONFIG_MPIC.
Signed-off-by: Harry Ciao <qingtao.cao@windriver.com>
---
drivers/edac/Makefile | 4 ++
drivers/edac/edac_mpic_irq.c | 86 ++++++++++++++++++++++++++++++++++++++++++
include/linux/edac.h | 20 ++++++++++
3 files changed, 110 insertions(+), 0 deletions(-)
create mode 100644 drivers/edac/edac_mpic_irq.c
diff --git a/drivers/edac/Makefile b/drivers/edac/Makefile
index 07a31cf..62778ee 100644
--- a/drivers/edac/Makefile
+++ b/drivers/edac/Makefile
@@ -17,6 +17,10 @@ ifdef CONFIG_PCI
edac_core-objs += edac_pci.o edac_pci_sysfs.o
endif
+ifdef CONFIG_MPIC
+edac_core-objs += edac_mpic_irq.o
+endif
+
obj-$(CONFIG_EDAC_AMD76X) += amd76x_edac.o
obj-$(CONFIG_EDAC_CPC925) += cpc925_edac.o
obj-$(CONFIG_EDAC_I5000) += i5000_edac.o
diff --git a/drivers/edac/edac_mpic_irq.c b/drivers/edac/edac_mpic_irq.c
new file mode 100644
index 0000000..7486b15
--- /dev/null
+++ b/drivers/edac/edac_mpic_irq.c
@@ -0,0 +1,86 @@
+/*
+ * edac_mpic_irq.c -
+ * Collect the machine specific code that creates the hwirq2virq
+ * mapping for the possible multiple EDAC modules on the same
+ * machine that supports MPIC.
+ *
+ * Copyright (c) 2009 Wind River Systems, Inc.
+ *
+ * Authors: Cao Qingtao <qingtao.cao@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/kernel.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/edac.h>
+
+#ifdef CONFIG_PPC_MAPLE
+static int edac_maple_get_irq(int hwirq)
+{
+ struct device_node *np, *mpic_node = NULL;
+ int irq = NO_IRQ;
+
+ /*
+ * Locate MPIC in the device-tree. Note that there is a bug
+ * in Maple device-tree where the type of the controller is
+ * open-pic and not interrupt-controller
+ */
+ for_each_node_by_type(np, "interrupt-controller") {
+ if (of_device_is_compatible(np, "open-pic")) {
+ mpic_node = np;
+ break;
+ }
+ }
+
+ if (mpic_node == NULL) {
+ for_each_node_by_type(np, "open-pic") {
+ mpic_node = np;
+ break;
+ }
+ }
+
+ if (mpic_node) {
+ irq = irq_create_of_mapping(mpic_node, &hwirq, 1);
+ of_node_put(mpic_node);
+ } else
+ printk(KERN_ERR "Failed to locate the MPIC DTB node\n");
+
+ return irq;
+}
+#endif
+
+/*
+ * NOTE:
+ * The EDAC driver should implement and register its machine-specific
+ * method to get a virtual IRQ here.
+ */
+int edac_get_mpic_irq(int hwirq)
+{
+ int virq = NO_IRQ;
+
+ if ((hwirq != MPIC_HWIRQ_HT_NMI) &&
+ (hwirq != MPIC_HWIRQ_INTERNAL_ERROR))
+ return NO_IRQ;
+
+#ifdef CONFIG_PPC_MAPLE
+ virq = edac_maple_get_irq(hwirq);
+#endif
+
+ return virq;
+
+}
+EXPORT_SYMBOL_GPL(edac_get_mpic_irq);
diff --git a/include/linux/edac.h b/include/linux/edac.h
index 7cf92e8..c122d22 100644
--- a/include/linux/edac.h
+++ b/include/linux/edac.h
@@ -38,4 +38,24 @@ static inline void opstate_init(void)
return;
}
+#ifdef CONFIG_MPIC
+enum {
+ /*
+ * Vector carried in southbridge NMI Request Messages
+ * posted through Hypertransport Channel
+ */
+ MPIC_HWIRQ_HT_NMI = 0,
+
+ /*
+ * Vector for MPIC Internal Error
+ */
+ MPIC_HWIRQ_INTERNAL_ERROR = 2,
+
+ MPIC_HWIRQS, /* must be the very last */
+};
+
+/* Create a hwirq2virq mapping for the specified hwirq */
+extern int edac_get_mpic_irq(int hwirq);
+#endif
+
#endif
--
1.5.6.2
^ permalink raw reply related
* [v1 PATCH 4/4] EDAC: INT mode support for AMD8131 driver
From: Harry Ciao @ 2009-05-18 10:05 UTC (permalink / raw)
To: benh, bluesmoke-devel; +Cc: linuxppc-dev, linux-kernel
In-Reply-To: <1242641100-15324-4-git-send-email-qingtao.cao@windriver.com>
Support EDAC INT mode for AMD8131 EDAC driver, which may post upstream
NMI interrupt request messages that will latch MPIC INT0 pin.
Following aspects for this patch have been tested:
1, module initialization and deletion for NMI mode;
2, creation and deletion for the mapping between hwirq==0 to a virq;
Note, due to the difficulty and complexity to generate a real hardware
EDAC Errors, below aspects have not been tested yet:
1, code that controls the generation of the NMI Request Message;
2, the mapping from the NMI Request Messages to MPIC INT0 pin;
3, if EDAC isr methods could handle errors correctly.
Signed-off-by: Harry Ciao <qingtao.cao@windriver.com>
---
drivers/edac/amd8131_edac.c | 169 ++++++++++++++++++++++++++++++++++++++-----
drivers/edac/amd8131_edac.h | 20 +++++-
2 files changed, 170 insertions(+), 19 deletions(-)
diff --git a/drivers/edac/amd8131_edac.c b/drivers/edac/amd8131_edac.c
index b432d60..9b8217b 100644
--- a/drivers/edac/amd8131_edac.c
+++ b/drivers/edac/amd8131_edac.c
@@ -28,6 +28,7 @@
#include <linux/bitops.h>
#include <linux/edac.h>
#include <linux/pci_ids.h>
+#include <linux/interrupt.h>
#include "edac_core.h"
#include "edac_module.h"
@@ -36,6 +37,11 @@
#define AMD8131_EDAC_REVISION " Ver: 1.0.0 " __DATE__
#define AMD8131_EDAC_MOD_STR "amd8131_edac"
+static int amd8131_op_state = EDAC_OPSTATE_POLL;
+module_param(amd8131_op_state, int, 0444);
+MODULE_PARM_DESC(amd8131_op_state, "EDAC Error Reporting state: 0=Poll, 1=NMI");
+static int amd8131_nmi_irq;
+
/* Wrapper functions for accessing PCI configuration space */
static void edac_pci_read_dword(struct pci_dev *dev, int reg, u32 *val32)
{
@@ -139,6 +145,17 @@ static void amd8131_pcix_init(struct amd8131_dev_info *dev_info)
edac_pci_read_dword(dev, REG_LNK_CTRL_B, &val32);
val32 |= LNK_CTRL_CRCFEN;
edac_pci_write_dword(dev, REG_LNK_CTRL_B, val32);
+
+ /* enable HT NMI messages generation on errors */
+ if (amd8131_op_state == EDAC_OPSTATE_NMI) {
+ edac_pci_read_dword(dev, REG_MISC_I, &val32);
+ val32 &= ~MISC_I_NIOAMODE;
+ edac_pci_write_dword(dev, REG_MISC_I, val32);
+
+ edac_pci_read_dword(dev, REG_MISC_II, &val32);
+ val32 |= MISC_II_NMIEN;
+ edac_pci_write_dword(dev, REG_MISC_II, val32);
+ }
}
static void amd8131_pcix_exit(struct amd8131_dev_info *dev_info)
@@ -165,6 +182,17 @@ static void amd8131_pcix_exit(struct amd8131_dev_info *dev_info)
edac_pci_read_dword(dev, REG_LNK_CTRL_B, &val32);
val32 &= ~LNK_CTRL_CRCFEN;
edac_pci_write_dword(dev, REG_LNK_CTRL_B, val32);
+
+ /* Disable HT NMI messages on errors*/
+ if (amd8131_op_state == EDAC_OPSTATE_NMI) {
+ edac_pci_read_dword(dev, REG_MISC_II, &val32);
+ val32 &= ~MISC_II_NMIEN;
+ edac_pci_write_dword(dev, REG_MISC_II, val32);
+
+ edac_pci_read_dword(dev, REG_MISC_I, &val32);
+ val32 |= MISC_I_NIOAMODE;
+ edac_pci_write_dword(dev, REG_MISC_I, val32);
+ }
}
static void amd8131_pcix_check(struct edac_pci_ctl_info *edac_dev)
@@ -233,12 +261,33 @@ static void amd8131_pcix_check(struct edac_pci_ctl_info *edac_dev)
}
}
+static irqreturn_t amd8131_pcix_isr(int irq, void *dev_id)
+{
+ struct edac_pci_ctl_info *edac_pci = dev_id;
+ struct amd8131_dev_info *dev_info = edac_pci->pvt_info;
+ struct pci_dev *dev = dev_info->dev;
+ u32 val32;
+
+ /*
+ * Only a handful of errors in PCI-X Bridge Memory Base-Limit
+ * Register could trigger NMI interrupt request message.
+ */
+ edac_pci_read_dword(dev, REG_MEM_LIM, &val32);
+ if (!(val32 & MEM_LIMIT_NMI_MASK))
+ return IRQ_NONE;
+
+ amd8131_pcix_check(edac_pci);
+
+ return IRQ_HANDLED;
+}
+
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,
+ .isr = amd8131_pcix_isr,
};
/*
@@ -249,6 +298,7 @@ static struct amd8131_info amd8131_chipset = {
static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
struct amd8131_dev_info *dev_info;
+ int ret = -ENODEV;
for (dev_info = amd8131_chipset.devices; dev_info->inst != NO_BRIDGE;
dev_info++)
@@ -256,7 +306,7 @@ static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
break;
if (dev_info->inst == NO_BRIDGE) /* should never happen */
- return -ENODEV;
+ goto out;
/*
* We can't call pci_get_device() as we are used to do because
@@ -265,12 +315,11 @@ static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
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;
+ goto err1;
}
/*
@@ -280,8 +329,10 @@ static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
*/
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;
+ if (!dev_info->edac_dev) {
+ ret = -ENOMEM;
+ goto err1;
+ }
dev_info->edac_dev->pvt_info = dev_info;
dev_info->edac_dev->dev = &dev_info->dev->dev;
@@ -289,7 +340,7 @@ static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
dev_info->edac_dev->ctl_name = dev_info->ctl_name;
dev_info->edac_dev->dev_name = dev_name(&dev_info->dev->dev);
- if (edac_op_state == EDAC_OPSTATE_POLL)
+ if (amd8131_op_state == EDAC_OPSTATE_POLL)
dev_info->edac_dev->edac_check = amd8131_chipset.check;
if (amd8131_chipset.init)
@@ -298,8 +349,8 @@ static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
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;
+ ret = -ENOMEM;
+ goto err2;
}
printk(KERN_INFO "added one device on AMD8131 "
@@ -307,7 +358,18 @@ static int amd8131_probe(struct pci_dev *dev, const struct pci_device_id *id)
PCI_VENDOR_ID_AMD, amd8131_chipset.err_dev,
dev_info->devfn, dev_info->ctl_name);
- return 0;
+ ret = 0;
+ goto out;
+
+err2:
+ if (amd8131_chipset.exit)
+ amd8131_chipset.exit(dev_info);
+
+ edac_pci_free_ctl_info(dev_info->edac_dev);
+err1:
+ pci_dev_put(dev_info->dev);
+out:
+ return ret;
}
static void amd8131_remove(struct pci_dev *dev)
@@ -322,14 +384,14 @@ static void amd8131_remove(struct pci_dev *dev)
if (dev_info->inst == NO_BRIDGE) /* should never happen */
return;
+ if (amd8131_chipset.exit)
+ amd8131_chipset.exit(dev_info);
+
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);
}
@@ -342,9 +404,7 @@ static const struct pci_device_id amd8131_edac_pci_tbl[] = {
.class_mask = 0,
.driver_data = 0,
},
- {
- 0,
- } /* table is NULL-terminated */
+ {0} /* table is NULL-terminated */
};
MODULE_DEVICE_TABLE(pci, amd8131_edac_pci_tbl);
@@ -355,20 +415,93 @@ static struct pci_driver amd8131_edac_driver = {
.id_table = amd8131_edac_pci_tbl,
};
+/*
+ * AMD8131 NMI handler - check PCI-X Bridges to claim any
+ * possible NMI instance.
+ * Southbridge NMI Request messages posted through Hypertransport
+ * Channel will be transferred to a MPIC interrupt instance.
+ *
+ * NOTE: According to AMD8131 data sheet 4.5.7 section,
+ * only a partial of error detections could generate NMI
+ * Upstream Hypertransport Interrupt request messages, so
+ * use NMI mode at sacrifice that not all error detections
+ * could be made use of.
+ */
+static irqreturn_t amd8131_nmi_handler(int irq, void *dev_id)
+{
+ struct amd8131_info *info = dev_id;
+ struct amd8131_dev_info *dev_info;
+ irqreturn_t ret = IRQ_NONE;
+
+ if (!info->isr)
+ return IRQ_NONE;
+
+ for (dev_info = info->devices; dev_info->inst != NO_BRIDGE; dev_info++)
+ ret |= info->isr(irq, dev_info->edac_dev);
+
+ return ret;
+}
+
+static void __init amd8131_nmi_handler_setup(void)
+{
+ int ret;
+
+ if (amd8131_op_state != EDAC_OPSTATE_NMI)
+ return;
+
+ amd8131_nmi_irq = NO_IRQ;
+
+#ifdef CONFIG_MPIC
+ amd8131_nmi_irq = edac_get_mpic_irq(MPIC_HWIRQ_HT_NMI);
+#endif
+
+ if (amd8131_nmi_irq == NO_IRQ) {
+ printk(KERN_ERR "%s: failed to get virq "
+ "for AMD8131 NMI requests\n", __func__);
+ return;
+ }
+
+ ret = request_irq(amd8131_nmi_irq, amd8131_nmi_handler,
+ IRQF_SHARED, "[EDAC] AMD8131", &amd8131_chipset);
+ if (ret < 0) {
+ printk(KERN_INFO "%s: failed to request irq %d for "
+ "AMD8131 NMI requests\n", __func__, amd8131_nmi_irq);
+ return;
+ }
+
+ debugf0("%s: Successfully requested irq %d for AMD8131 NMI requests\n",
+ __func__, amd8131_nmi_irq);
+}
+
+static void __exit amd8131_nmi_handler_exit(void)
+{
+ if (amd8131_op_state != EDAC_OPSTATE_NMI)
+ return;
+
+ if (amd8131_nmi_irq != NO_IRQ)
+ free_irq(amd8131_nmi_irq, &amd8131_chipset);
+}
+
static int __init amd8131_edac_init(void)
{
+ int ret;
+
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;
+ ret = pci_register_driver(&amd8131_edac_driver);
- return pci_register_driver(&amd8131_edac_driver);
+ if (ret == 0)
+ amd8131_nmi_handler_setup();
+
+ return ret;
}
static void __exit amd8131_edac_exit(void)
{
pci_unregister_driver(&amd8131_edac_driver);
+
+ amd8131_nmi_handler_exit();
}
module_init(amd8131_edac_init);
diff --git a/drivers/edac/amd8131_edac.h b/drivers/edac/amd8131_edac.h
index 60e0d1c..7e86cbf 100644
--- a/drivers/edac/amd8131_edac.h
+++ b/drivers/edac/amd8131_edac.h
@@ -61,7 +61,8 @@ enum mem_limit_bits {
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
+ MEM_LIMIT_RTA|MEM_LIMIT_STA|MEM_LIMIT_MDPE,
+ MEM_LIMIT_NMI_MASK = MEM_LIMIT_DPE | MEM_LIMIT_RSE
};
/************************************************************
@@ -80,6 +81,22 @@ enum lnk_ctrl_bits {
LNK_CTRL_CRCFEN = BIT(1)
};
+/************************************************************
+ * PCI-X Miscellaneous Register, Dev[B,A]:0x40
+ ************************************************************/
+#define REG_MISC_I 0x40
+enum misc_i_bits {
+ MISC_I_NIOAMODE = BIT(0),
+};
+
+/************************************************************
+ * PCI-X Miscellaneous II Register, Dev[B,A]:0x44
+ ************************************************************/
+#define REG_MISC_II 0x44
+enum misc_ii_bits {
+ MISC_II_NMIEN = BIT(0),
+};
+
enum pcix_bridge_inst {
NORTH_A = 0,
NORTH_B = 1,
@@ -113,6 +130,7 @@ struct amd8131_info {
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);
+ irqreturn_t (*isr)(int irq, void *dev_id);
};
#endif /* _AMD8131_EDAC_H_ */
--
1.5.6.2
^ permalink raw reply related
* [v1 PATCH 3/4] EDAC: INT mode support for AMD8111 driver
From: Harry Ciao @ 2009-05-18 10:04 UTC (permalink / raw)
To: benh, bluesmoke-devel; +Cc: linuxppc-dev, linux-kernel
In-Reply-To: <1242641100-15324-3-git-send-email-qingtao.cao@windriver.com>
Support EDAC INT mode for AMD8111 EDAC driver, which may post upstream
NMI interrupt request messages that will latch MPIC INT0 pin.
Following aspects for this patch have been tested:
1, module initialization and deletion for NMI mode;
2, creation and deletion for the mapping between hwirq==0 to a virq;
Note, due to the difficulty and complexity to generate a real hardware
EDAC Errors, below aspects have not been tested yet:
1, code that controls the generation of the NMI Request Message;
2, the mapping from the NMI Request Messages to MPIC INT0 pin;
3, if EDAC isr methods could handle errors correctly.
Signed-off-by: Harry Ciao <qingtao.cao@windriver.com>
---
drivers/edac/amd8111_edac.c | 348 +++++++++++++++++++++++++++++++++++++------
drivers/edac/amd8111_edac.h | 43 +++++-
2 files changed, 343 insertions(+), 48 deletions(-)
diff --git a/drivers/edac/amd8111_edac.c b/drivers/edac/amd8111_edac.c
index 35b78d0..10dc8ac 100644
--- a/drivers/edac/amd8111_edac.c
+++ b/drivers/edac/amd8111_edac.c
@@ -38,6 +38,11 @@
#define PCI_DEVICE_ID_AMD_8111_PCI 0x7460
+static int amd8111_op_state = EDAC_OPSTATE_POLL;
+module_param(amd8111_op_state, int, 0444);
+MODULE_PARM_DESC(amd8111_op_state, "EDAC Error Reporting state: 0=Poll, 1=NMI");
+static int amd8111_nmi_irq;
+
enum amd8111_edac_devs {
LPC_BRIDGE = 0,
};
@@ -89,10 +94,9 @@ static void edac_pci_write_byte(struct pci_dev *dev, int reg, u8 val8)
" PCI Access Write Error at 0x%x\n", reg);
}
+/* device specific methods for AMD8111 PCI Bridge device */
/*
- * device-specific methods for amd8111 PCI Bridge Controller
- *
- * Error Reporting and Handling for amd8111 chipset could be found
+ * 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)
@@ -125,7 +129,7 @@ static void amd8111_pci_bridge_init(struct amd8111_pci_info *pci_info)
edac_pci_write_dword(dev, REG_PCI_INTBRG_CTRL, val32);
/* Last enable error detections */
- if (edac_op_state == EDAC_OPSTATE_POLL) {
+ if (amd8111_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;
@@ -140,6 +144,11 @@ static void amd8111_pci_bridge_init(struct amd8111_pci_info *pci_info)
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);
+ } else if (amd8111_op_state == EDAC_OPSTATE_NMI) {
+ /* Enable Parity Error detection on secondary PCI bus */
+ edac_pci_read_dword(dev, REG_PCI_INTBRG_CTRL, &val32);
+ val32 |= PCI_INTBRG_CTRL_PEREN;
+ edac_pci_write_dword(dev, REG_PCI_INTBRG_CTRL, val32);
}
}
@@ -148,7 +157,7 @@ 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) {
+ if (amd8111_op_state == EDAC_OPSTATE_POLL) {
/* Disable System Error reporting */
edac_pci_read_dword(dev, REG_PCI_STSCMD, &val32);
val32 &= ~PCI_STSCMD_SERREN;
@@ -163,6 +172,11 @@ static void amd8111_pci_bridge_exit(struct amd8111_pci_info *pci_info)
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);
+ } else if (amd8111_op_state == EDAC_OPSTATE_NMI) {
+ /* Disable Parity Error detection on secondary PCI bus */
+ edac_pci_read_dword(dev, REG_PCI_INTBRG_CTRL, &val32);
+ val32 &= ~PCI_INTBRG_CTRL_PEREN;
+ edac_pci_write_dword(dev, REG_PCI_INTBRG_CTRL, val32);
}
}
@@ -238,11 +252,136 @@ static void amd8111_pci_bridge_check(struct edac_pci_ctl_info *edac_dev)
}
}
+static irqreturn_t amd8111_pci_bridge_isr(int irq, void *dev_id)
+{
+ struct edac_pci_ctl_info *edac_dev = dev_id;
+ struct amd8111_pci_info *pci_info = edac_dev->pvt_info;
+ struct pci_dev *dev = pci_info->dev;
+ u32 stscmd, htlink, intbrg, memlim;
+
+ edac_pci_read_dword(dev, REG_PCI_STSCMD, &stscmd);
+ edac_pci_read_dword(dev, REG_HT_LINK, &htlink);
+ edac_pci_read_dword(dev, REG_PCI_INTBRG_CTRL, &intbrg);
+ edac_pci_read_dword(dev, REG_MEM_LIM, &memlim);
+
+ if (!((stscmd & PCI_STSCMD_NMI_MASK) ||
+ (htlink & HT_LINK_CRCERR) ||
+ (intbrg & PCI_INTBRG_CTRL_DTSTAT) ||
+ (memlim & MEM_LIMIT_CLEAR_MASK)))
+ return IRQ_NONE;
+
+ amd8111_pci_bridge_check(edac_dev);
+
+ return IRQ_HANDLED;
+}
+
+/* device specific methods for AMD8111 LPC Bridge device */
+/*
+ * According to AMD8111 datasheet 3.4.2.4 section, NMI is controlled
+ * by following equation:
+ * NMI = ~PORT70[NMIDIS] &
+ * (PM48[NMI_NOW] | ~PM48[NMI2SMI_EN] &
+ * (PORT61[SERR] & ~PORT61[CLRSERR]
+ * | PORT61[IOCHK] & ~PORT61[CLRIOCHK]
+ * | DevB:0x40[NMIONERR] & [status bits described in section 3.1.2]
+ * | DevA:0x1C[MDPE] & DevA:0x3C[PEREN]));
+ *
+ * PORT70[NMIDIS] and PM48[NMI2SMI_EN] will be turned off here
+ * if necessary, the rest of device-specific NMI control bits
+ * will be set separately.
+ */
+static int amd8111_NMI_global_enable(struct pci_dev *lpc_dev)
+{
+ struct pci_dev *dev = lpc_dev;
+ u8 val8;
+ u16 val16;
+ u32 val32, mapbase;
+ void __iomem *mmio_vbase;
+
+ /*
+ * Global NMI disablement status could be read from
+ * DevB:0x41[NMIDIS], clear PORT70[NMIDIS] only when
+ * DevB:0x41[NMIDIS] is set.
+ */
+ edac_pci_read_byte(dev, REG_IO_CTRL_2, &val8);
+ if (val8 & IO_CTRL_2_NMIDIS) {
+ val8 = __do_inb(REG_RTC);
+ val8 &= ~RTC_NMIDIS;
+ __do_outb(val8, REG_RTC);
+ }
+
+ /*
+ * The start address of the 256-byte relocatable System Management
+ * I/O register block is specified by DevB:3x58[PMBASE], and
+ * accessing this MMIO region is controlled by DevB:3x41[PMIOEN].
+ */
+ dev = pci_get_device(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS,
+ NULL);
+ if (!dev) {
+ printk(KERN_ERR "%s: AMD8111 NMI control device not found: "
+ "vendor %x, device %x\n", __func__,
+ PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS);
+ return -ENODEV;
+ }
+
+ if (pci_enable_device(dev)) {
+ pci_dev_put(dev);
+ printk(KERN_ERR "%s: failed to enable: "
+ "vendor %x, device %x\n", __func__,
+ PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_8111_SMBUS);
+ return -ENODEV;
+ }
+
+ edac_pci_read_byte(dev, REG_GEN_CONFIG_2, &val8);
+ if (!(val8 & GEN_CONFIG_2_PMIOEN)) {
+ val8 |= GEN_CONFIG_2_PMIOEN;
+ edac_pci_write_byte(dev, REG_GEN_CONFIG_2, val8);
+ }
+
+ /*
+ * get the physical address of the relocatable 256-byte
+ * System Management I/O register block.
+ */
+ edac_pci_read_dword(dev, REG_SYSMAN_IO_SPACE, &val32);
+ mapbase = val32 & SYSMAN_IO_SPACE_PMBASE_MASK;
+ mapbase += dev->bus->resource[0]->start;
+
+ if (!request_mem_region(mapbase, AMD8111_SYSMAN_IO_SIZE,
+ "amd8111_PMxx")) {
+ pci_dev_put(dev);
+ printk(KERN_ERR "%s: failed to request region\n", __func__);
+ return -EBUSY;
+ }
+
+ mmio_vbase = ioremap(mapbase, AMD8111_SYSMAN_IO_SIZE);
+ if (!mmio_vbase) {
+ printk(KERN_ERR "%s: failed to ioremap region: "
+ "address 0x%x, len 0x%x\n", __func__,
+ mapbase, AMD8111_SYSMAN_IO_SIZE);
+ pci_dev_put(dev);
+ return -ENOMEM;
+ }
+
+ /* clear PM48[NMI2SMI_EN] if necessary */
+ val16 = in_le16(mmio_vbase + IO_TCO_CTRL_1);
+ if (val16 & IO_TCO_CTRL_1_NMI2SMI_EN) {
+ val16 &= ~IO_TCO_CTRL_1_NMI2SMI_EN;
+ out_le16(mmio_vbase + IO_TCO_CTRL_1, val16);
+ printk(KERN_INFO "%s: PM48[NMI2SMI_EN] is cleared\n", __func__);
+ }
+
+ iounmap(mmio_vbase);
+ release_mem_region(mapbase, AMD8111_SYSMAN_IO_SIZE);
+
+ pci_dev_put(dev);
+
+ return 0;
+}
+
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;
@@ -278,10 +417,29 @@ static void amd8111_lpc_bridge_init(struct amd8111_dev_info *dev_info)
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);
+
+ if (amd8111_op_state == EDAC_OPSTATE_NMI) {
+ /* Enable NMI generation on errors */
+ edac_pci_read_byte(dev, REG_IO_CTRL_1, &val8);
+ val8 |= IO_CTRL_1_NMIONERR;
+ edac_pci_write_byte(dev, REG_IO_CTRL_1, val8);
+
+ amd8111_NMI_global_enable(dev);
+ }
}
static void amd8111_lpc_bridge_exit(struct amd8111_dev_info *dev_info)
{
+ u8 val8;
+ struct pci_dev *dev = dev_info->dev;
+
+ if (amd8111_op_state == EDAC_OPSTATE_NMI) {
+ /* Disable NMI generation on errors */
+ edac_pci_read_byte(dev, REG_IO_CTRL_1, &val8);
+ val8 &= ~IO_CTRL_1_NMIONERR;
+ edac_pci_write_byte(dev, REG_IO_CTRL_1, val8);
+ }
+
if (legacy_io_res)
release_region(REG_AT_COMPAT, LEGACY_NR_PORTS);
}
@@ -322,6 +480,22 @@ static void amd8111_lpc_bridge_check(struct edac_device_ctl_info *edac_dev)
}
}
+static irqreturn_t amd8111_lpc_bridge_isr(int irq, void *dev_id)
+{
+ struct edac_device_ctl_info *edac_dev = dev_id;
+ 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))
+ return IRQ_NONE;
+
+ amd8111_lpc_bridge_check(edac_dev);
+
+ return IRQ_HANDLED;
+}
+
/* General devices represented by edac_device_ctl_info */
static struct amd8111_dev_info amd8111_devices[] = {
[LPC_BRIDGE] = {
@@ -330,6 +504,7 @@ static struct amd8111_dev_info amd8111_devices[] = {
.init = amd8111_lpc_bridge_init,
.exit = amd8111_lpc_bridge_exit,
.check = amd8111_lpc_bridge_check,
+ .isr = amd8111_lpc_bridge_isr,
},
{0},
};
@@ -342,6 +517,7 @@ static struct amd8111_pci_info amd8111_pcis[] = {
.init = amd8111_pci_bridge_init,
.exit = amd8111_pci_bridge_exit,
.check = amd8111_pci_bridge_check,
+ .isr = amd8111_pci_bridge_isr,
},
{0},
};
@@ -350,25 +526,24 @@ 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];
+ int ret = -ENODEV;
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;
+ goto out;
}
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;
+ goto err1;
}
/*
@@ -381,8 +556,10 @@ static int amd8111_dev_probe(struct pci_dev *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;
+ if (!dev_info->edac_dev) {
+ ret = -ENOMEM;
+ goto err1;
+ }
dev_info->edac_dev->pvt_info = dev_info;
dev_info->edac_dev->dev = &dev_info->dev->dev;
@@ -390,7 +567,7 @@ static int amd8111_dev_probe(struct pci_dev *dev,
dev_info->edac_dev->ctl_name = dev_info->ctl_name;
dev_info->edac_dev->dev_name = dev_name(&dev_info->dev->dev);
- if (edac_op_state == EDAC_OPSTATE_POLL)
+ if (amd8111_op_state == EDAC_OPSTATE_POLL)
dev_info->edac_dev->edac_check = dev_info->check;
if (dev_info->init)
@@ -399,16 +576,27 @@ static int amd8111_dev_probe(struct pci_dev *dev,
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;
+ ret = -ENOMEM;
+ goto err2;
}
- printk(KERN_INFO "added one edac_dev on AMD8111 "
+ printk(KERN_INFO "added one device on AMD8111 "
"vendor %x, device %x, name %s\n",
PCI_VENDOR_ID_AMD, dev_info->err_dev,
dev_info->ctl_name);
- return 0;
+ ret = 0;
+ goto out;
+
+err2:
+ if (dev_info->exit)
+ dev_info->exit(dev_info);
+
+ edac_device_free_ctl_info(dev_info->edac_dev);
+err1:
+ pci_dev_put(dev_info->dev);
+out:
+ return ret;
}
static void amd8111_dev_remove(struct pci_dev *dev)
@@ -422,14 +610,14 @@ static void amd8111_dev_remove(struct pci_dev *dev)
if (!dev_info->err_dev) /* should never happen */
return;
+ if (dev_info->exit)
+ dev_info->exit(dev_info);
+
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);
}
@@ -437,25 +625,24 @@ 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];
+ int ret = -ENODEV;
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;
+ goto out;
}
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;
+ goto err1;
}
/*
@@ -465,8 +652,10 @@ static int amd8111_pci_probe(struct pci_dev *dev,
*/
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;
+ if (!pci_info->edac_dev) {
+ ret = -ENOMEM;
+ goto err1;
+ }
pci_info->edac_dev->pvt_info = pci_info;
pci_info->edac_dev->dev = &pci_info->dev->dev;
@@ -474,7 +663,7 @@ static int amd8111_pci_probe(struct pci_dev *dev,
pci_info->edac_dev->ctl_name = pci_info->ctl_name;
pci_info->edac_dev->dev_name = dev_name(&pci_info->dev->dev);
- if (edac_op_state == EDAC_OPSTATE_POLL)
+ if (amd8111_op_state == EDAC_OPSTATE_POLL)
pci_info->edac_dev->edac_check = pci_info->check;
if (pci_info->init)
@@ -483,16 +672,27 @@ static int amd8111_pci_probe(struct pci_dev *dev,
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;
+ ret = -ENOMEM;
+ goto err2;
}
- printk(KERN_INFO "added one edac_pci on AMD8111 "
+ printk(KERN_INFO "added one device on AMD8111 "
"vendor %x, device %x, name %s\n",
PCI_VENDOR_ID_AMD, pci_info->err_dev,
pci_info->ctl_name);
- return 0;
+ ret = 0;
+ goto out;
+
+err2:
+ if (pci_info->exit)
+ pci_info->exit(pci_info);
+
+ edac_pci_free_ctl_info(pci_info->edac_dev);
+err1:
+ pci_dev_put(pci_info->dev);
+out:
+ return ret;
}
static void amd8111_pci_remove(struct pci_dev *dev)
@@ -506,14 +706,14 @@ static void amd8111_pci_remove(struct pci_dev *dev)
if (!pci_info->err_dev) /* should never happen */
return;
+ if (pci_info->exit)
+ pci_info->exit(pci_info);
+
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);
}
@@ -527,9 +727,7 @@ static const struct pci_device_id amd8111_edac_dev_tbl[] = {
.class_mask = 0,
.driver_data = LPC_BRIDGE,
},
- {
- 0,
- } /* table is NULL-terminated */
+ {0} /* table is NULL-terminated */
};
MODULE_DEVICE_TABLE(pci, amd8111_edac_dev_tbl);
@@ -550,9 +748,7 @@ static const struct pci_device_id amd8111_edac_pci_tbl[] = {
.class_mask = 0,
.driver_data = PCI_BRIDGE,
},
- {
- 0,
- } /* table is NULL-terminated */
+ {0} /* table is NULL-terminated */
};
MODULE_DEVICE_TABLE(pci, amd8111_edac_pci_tbl);
@@ -563,6 +759,69 @@ static struct pci_driver amd8111_edac_pci_driver = {
.id_table = amd8111_edac_pci_tbl,
};
+/*
+ * AMD8111 NMI handler - check Legacy ISA Bridge and PCI Bridge
+ * to claim any possible NMI instance.
+ * Southbridge NMI Request messages posted through Hypertransport
+ * Channel will be transferred to a MPIC interrupt instance.
+ */
+static irqreturn_t amd8111_nmi_handler(int irq, void *dev_id)
+{
+ struct amd8111_dev_info *dev_info;
+ struct amd8111_pci_info *pci_info;
+ irqreturn_t ret = IRQ_NONE;
+
+ for (dev_info = amd8111_devices; dev_info->err_dev; dev_info++)
+ if (dev_info->isr)
+ ret |= dev_info->isr(irq, dev_info->edac_dev);
+
+ for (pci_info = amd8111_pcis; pci_info->err_dev; pci_info++)
+ if (pci_info->isr)
+ ret |= pci_info->isr(irq, pci_info->edac_dev);
+
+ return ret;
+}
+
+static void __init amd8111_nmi_handler_setup(void)
+{
+ int ret;
+
+ if (amd8111_op_state != EDAC_OPSTATE_NMI)
+ return;
+
+ amd8111_nmi_irq = NO_IRQ;
+
+#ifdef CONFIG_MPIC
+ amd8111_nmi_irq = edac_get_mpic_irq(MPIC_HWIRQ_HT_NMI);
+#endif
+
+ if (amd8111_nmi_irq == NO_IRQ) {
+ printk(KERN_ERR "%s: failed to get virq "
+ "for AMD8111 NMI requests\n", __func__);
+ return;
+ }
+
+ ret = request_irq(amd8111_nmi_irq, amd8111_nmi_handler,
+ IRQF_SHARED, "[EDAC] AMD8111", amd8111_devices);
+ if (ret < 0) {
+ printk(KERN_INFO "%s: failed to request irq %d for "
+ "AMD8111 NMI requests\n", __func__, amd8111_nmi_irq);
+ return;
+ }
+
+ debugf0("%s: Successfully requested irq %d for AMD8111 NMI requests\n",
+ __func__, amd8131_nmi_irq);
+}
+
+static void __exit amd8111_nmi_handler_exit(void)
+{
+ if (amd8111_op_state != EDAC_OPSTATE_NMI)
+ return;
+
+ if (amd8111_nmi_irq != NO_IRQ)
+ free_irq(amd8111_nmi_irq, amd8111_devices);
+}
+
static int __init amd8111_edac_init(void)
{
int val;
@@ -570,12 +829,12 @@ static int __init amd8111_edac_init(void)
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);
+ if (val == 0)
+ amd8111_nmi_handler_setup();
+
return val;
}
@@ -583,8 +842,9 @@ static void __exit amd8111_edac_exit(void)
{
pci_unregister_driver(&amd8111_edac_pci_driver);
pci_unregister_driver(&amd8111_edac_dev_driver);
-}
+ amd8111_nmi_handler_exit();
+}
module_init(amd8111_edac_init);
module_exit(amd8111_edac_exit);
diff --git a/drivers/edac/amd8111_edac.h b/drivers/edac/amd8111_edac.h
index 3579433..51776b1 100644
--- a/drivers/edac/amd8111_edac.h
+++ b/drivers/edac/amd8111_edac.h
@@ -33,9 +33,8 @@ enum pci_stscmd_bits {
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_STSCMD_NMI_MASK = (PCI_STSCMD_RMA | PCI_STSCMD_RTA),
+ PCI_STSCMD_CLEAR_MASK = (PCI_STSCMD_SSE | PCI_STSCMD_NMI_MASK),
};
/************************************************************
@@ -62,9 +61,10 @@ enum mem_limit_bits {
************************************************************/
#define REG_HT_LINK 0xc4
enum ht_link_bits {
+ HT_LINK_CRCERR = BIT(8),
HT_LINK_LKFAIL = BIT(4),
HT_LINK_CRCFEN = BIT(1),
- HT_LINK_CLEAR_MASK = (HT_LINK_LKFAIL)
+ HT_LINK_CLEAR_MASK = (HT_LINK_LKFAIL | HT_LINK_CRCERR)
};
/************************************************************
@@ -105,6 +105,39 @@ enum at_compat_bits {
AT_COMPAT_CLRSERR = BIT(2),
};
+#define REG_IO_CTRL_2 0x41
+enum io_ctrl_2_bits {
+ IO_CTRL_2_NMIDIS = BIT(1),
+};
+
+/************************************************************
+ * System Management Configuration Registers, DevB:3xXX
+ ************************************************************/
+#define REG_GEN_CONFIG_2 0x41
+enum gen_config_2_bits {
+ GEN_CONFIG_2_PMIOEN = BIT(7),
+};
+
+#define REG_SYSMAN_IO_SPACE 0x58
+#define SYSMAN_IO_SPACE_PMBASE_MASK 0xff00
+
+/************************************************************
+ * System Management I/O Space, PMxx
+ ************************************************************/
+#define AMD8111_SYSMAN_IO_SIZE 256
+#define IO_TCO_CTRL_1 0x48
+enum io_tco_ctrl_1_bits {
+ IO_TCO_CTRL_1_NMI2SMI_EN = BIT(9),
+};
+
+/************************************************************
+ * Real-Time Clock Port I/O
+ ************************************************************/
+#define REG_RTC 0x70
+enum rtc_bits {
+ RTC_NMIDIS = BIT(7),
+};
+
struct amd8111_dev_info {
u16 err_dev; /* PCI Device ID */
struct pci_dev *dev;
@@ -114,6 +147,7 @@ struct amd8111_dev_info {
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);
+ irqreturn_t (*isr)(int irq, void *dev_id);
};
struct amd8111_pci_info {
@@ -125,6 +159,7 @@ struct amd8111_pci_info {
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);
+ irqreturn_t (*isr)(int irq, void *dev_id);
};
#endif /* _AMD8111_EDAC_H_ */
--
1.5.6.2
^ permalink raw reply related
* [v1 PATCH 2/4] EDAC: MCE & INT mode support for CPC925 driver
From: Harry Ciao @ 2009-05-18 10:04 UTC (permalink / raw)
To: benh, bluesmoke-devel; +Cc: linuxppc-dev, linux-kernel
In-Reply-To: <1242641100-15324-2-git-send-email-qingtao.cao@windriver.com>
Support EDAC INT mode and add a new EDAC MCE mode for CPC925 EDAC driver.
CPC925 Hypertransport hostbridge controller may trigger interrupt that
latches MPIC INT2 pin on Hypertransport Link Errors, and generate MCE on
memory ECC Errors and Processor Interface Errors.
The global variable "edac_op_state" defined by EDAC core will be
obsolete, not only different EDAC modules on the same machine may
operate in different EDAC modes, but further this could be the
case for different EDAC devices of the same EDAC module, for example,
each CPC925 EDAC device could work in the mode specified by their own
"op_state" member in their private structure.
A spinlock will be used to protect the EDAC MCE handler from being
silently unregistered, however, it also implies a constraint that
when EDAC MCE handler is called on one CPU, it will be bypassed by
another MCE event on other CPUs.
Following aspects for this patch have been tested:
1, module initialization and deletion;
2, creation and deletion for the mapping between hwirq==2 to a virq
for the Hypertransport Link Errors;
3, registration and unregistration for the EDAC MCE handler from the
generic MCE handler on PPC;
Note, due to the difficulty and complexity to generate a real hardware
ECC/HT Link/CPU Errors, below aspects have not been tested yet:
1, if ECC or CPU Errors would generate MCE event;
2, if HT Link Error will indeed latch MPIC INT2 pin;
3, if EDAC isr/mce methods could handle errors correctly.
Signed-off-by: Harry Ciao <qingtao.cao@windriver.com>
---
arch/powerpc/kernel/traps.c | 16 +++
drivers/edac/cpc925_edac.c | 275 ++++++++++++++++++++++++++++++++++++++++---
drivers/edac/edac_stub.c | 6 +
include/linux/edac.h | 6 +
4 files changed, 284 insertions(+), 19 deletions(-)
diff --git a/arch/powerpc/kernel/traps.c b/arch/powerpc/kernel/traps.c
index 678fbff..1ae3465 100644
--- a/arch/powerpc/kernel/traps.c
+++ b/arch/powerpc/kernel/traps.c
@@ -57,6 +57,10 @@
#include <asm/dbell.h>
#endif
+#ifdef CONFIG_EDAC
+#include <linux/edac.h>
+#endif
+
#if defined(CONFIG_DEBUGGER) || defined(CONFIG_KEXEC)
int (*__debugger)(struct pt_regs *regs);
int (*__debugger_ipi)(struct pt_regs *regs);
@@ -481,6 +485,18 @@ int machine_check_generic(struct pt_regs *regs)
default:
printk("Unknown values in msr\n");
}
+
+#ifdef CONFIG_EDAC
+ if (spin_trylock(&edac_mce_lock)) {
+ if (edac_mce_handler) {
+ int ret = edac_mce_handler();
+ spin_unlock(&edac_mce_lock);
+ return ret;
+ }
+ spin_unlock(&edac_mce_lock);
+ }
+#endif
+
return 0;
}
#endif /* everything else */
diff --git a/drivers/edac/cpc925_edac.c b/drivers/edac/cpc925_edac.c
index 8c54196..36fc506 100644
--- a/drivers/edac/cpc925_edac.c
+++ b/drivers/edac/cpc925_edac.c
@@ -25,6 +25,8 @@
#include <linux/edac.h>
#include <linux/of.h>
#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <asm/reg.h>
#include "edac_core.h"
#include "edac_module.h"
@@ -273,22 +275,29 @@ enum brgctrl_bits {
/* Private structure for edac memory controller */
struct cpc925_mc_pdata {
+ int op_state;
void __iomem *vbase;
unsigned long total_mem;
const char *name;
int edac_idx;
+ struct mem_ctl_info *mci;
+ int (*mce)(struct mem_ctl_info *mci);
};
/* Private structure for common edac device */
struct cpc925_dev_info {
+ int op_state;
void __iomem *vbase;
struct platform_device *pdev;
char *ctl_name;
int edac_idx;
struct edac_device_ctl_info *edac_dev;
+ int irq;
void (*init)(struct cpc925_dev_info *dev_info);
void (*exit)(struct cpc925_dev_info *dev_info);
void (*check)(struct edac_device_ctl_info *edac_dev);
+ int (*mce)(struct edac_device_ctl_info *edac_dev);
+ irqreturn_t (*isr)(int irq, void *dev_id);
};
/* Get total memory size from Open Firmware DTB */
@@ -382,6 +391,18 @@ static void cpc925_init_csrows(struct mem_ctl_info *mci)
}
}
+/* Set up HID0_EMCP bit if necessary, MSR[ME] has been set up */
+static void cpc925_mce_enable(void)
+{
+ unsigned long hid0 = mfspr(SPRN_HID0);
+
+ if ((hid0 & HID0_EMCP) == 0)
+ mtspr(SPRN_HID0, hid0 | HID0_EMCP);
+
+ debugf0("%s: MSR[ME] = %d, HID0[EMCP] = %d\n", __func__,
+ mfmsr() & MSR_ME, mfspr(SPRN_HID0));
+}
+
/* Enable memory controller ECC detection */
static void cpc925_mc_init(struct mem_ctl_info *mci)
{
@@ -402,6 +423,9 @@ static void cpc925_mc_init(struct mem_ctl_info *mci)
mccr |= MCCR_ECC_EN;
__raw_writel(mccr, pdata->vbase + REG_MCCR_OFFSET);
}
+
+ if (pdata->op_state == EDAC_OPSTATE_MCE)
+ cpc925_mce_enable();
}
/* Disable memory controller ECC detection */
@@ -520,7 +544,10 @@ static int cpc925_mc_find_channel(struct mem_ctl_info *mci, u16 syndrome)
return 1;
}
-/* Check memory controller registers for ECC errors */
+/*
+ * Check memory controller registers for ECC errors,
+ * called when EDAC MC works in POLL mode.
+ */
static void cpc925_mc_check(struct mem_ctl_info *mci)
{
struct cpc925_mc_pdata *pdata = mci->pvt_info;
@@ -579,6 +606,70 @@ static void cpc925_mc_check(struct mem_ctl_info *mci)
syndrome);
}
+/*
+ * Check memory controller registers for ECC errors,
+ * called when EDAC MC works in MCE mode.
+ */
+static int cpc925_mc_mce(struct mem_ctl_info *mci)
+{
+ struct cpc925_mc_pdata *pdata = mci->pvt_info;
+ u32 apiexcp;
+ u32 mear;
+ u32 mesr;
+ u16 syndrome;
+ unsigned long pfn = 0, offset = 0;
+ int csrow = 0, channel = 0;
+
+ /* APIEXCP is cleared when read */
+ apiexcp = __raw_readl(pdata->vbase + REG_APIEXCP_OFFSET);
+ if ((apiexcp & ECC_EXCP_DETECTED) == 0)
+ return 0;
+
+ mesr = __raw_readl(pdata->vbase + REG_MESR_OFFSET);
+ syndrome = mesr | (MESR_ECC_SYN_H_MASK | MESR_ECC_SYN_L_MASK);
+
+ mear = __raw_readl(pdata->vbase + REG_MEAR_OFFSET);
+
+ /* Revert column/row addresses into page frame number, etc */
+ cpc925_mc_get_pfn(mci, mear, &pfn, &offset, &csrow);
+
+ if (apiexcp & CECC_EXCP_DETECTED) {
+ cpc925_mc_printk(mci, KERN_EMERG, "DRAM CECC Fault\n");
+ channel = cpc925_mc_find_channel(mci, syndrome);
+ edac_mc_handle_ce(mci, pfn, offset, syndrome,
+ csrow, channel, mci->ctl_name);
+ }
+
+ if (apiexcp & UECC_EXCP_DETECTED) {
+ cpc925_mc_printk(mci, KERN_EMERG, "DRAM UECC Fault\n");
+ edac_mc_handle_ue(mci, pfn, offset, csrow, mci->ctl_name);
+ }
+
+ cpc925_mc_printk(mci, KERN_EMERG, "Dump registers:\n");
+ cpc925_mc_printk(mci, KERN_EMERG, "APIMASK 0x%08x\n",
+ __raw_readl(pdata->vbase + REG_APIMASK_OFFSET));
+ cpc925_mc_printk(mci, KERN_EMERG, "APIEXCP 0x%08x\n",
+ apiexcp);
+ cpc925_mc_printk(mci, KERN_EMERG, "Mem Scrub Ctrl 0x%08x\n",
+ __raw_readl(pdata->vbase + REG_MSCR_OFFSET));
+ cpc925_mc_printk(mci, KERN_EMERG, "Mem Scrub Rge Start 0x%08x\n",
+ __raw_readl(pdata->vbase + REG_MSRSR_OFFSET));
+ cpc925_mc_printk(mci, KERN_EMERG, "Mem Scrub Rge End 0x%08x\n",
+ __raw_readl(pdata->vbase + REG_MSRER_OFFSET));
+ cpc925_mc_printk(mci, KERN_EMERG, "Mem Scrub Pattern 0x%08x\n",
+ __raw_readl(pdata->vbase + REG_MSPR_OFFSET));
+ cpc925_mc_printk(mci, KERN_EMERG, "Mem Chk Ctrl 0x%08x\n",
+ __raw_readl(pdata->vbase + REG_MCCR_OFFSET));
+ cpc925_mc_printk(mci, KERN_EMERG, "Mem Chk Rge End 0x%08x\n",
+ __raw_readl(pdata->vbase + REG_MCRER_OFFSET));
+ cpc925_mc_printk(mci, KERN_EMERG, "Mem Err Address 0x%08x\n",
+ mesr);
+ cpc925_mc_printk(mci, KERN_EMERG, "Mem Err Syndrome 0x%08x\n",
+ syndrome);
+
+ return 1;
+}
+
/******************** CPU err device********************************/
/* Enable CPU Errors detection */
static void cpc925_cpu_init(struct cpc925_dev_info *dev_info)
@@ -609,7 +700,7 @@ static void cpc925_cpu_exit(struct cpc925_dev_info *dev_info)
return;
}
-/* Check for CPU Errors */
+/* Check for CPU Errors, called in POLL mode */
static void cpc925_cpu_check(struct edac_device_ctl_info *edac_dev)
{
struct cpc925_dev_info *dev_info = edac_dev->pvt_info;
@@ -630,6 +721,28 @@ static void cpc925_cpu_check(struct edac_device_ctl_info *edac_dev)
edac_device_handle_ue(edac_dev, 0, 0, edac_dev->ctl_name);
}
+/* Check for CPU Errors, called in MCE mode */
+static int cpc925_cpu_mce(struct edac_device_ctl_info *edac_dev)
+{
+ struct cpc925_dev_info *dev_info = edac_dev->pvt_info;
+ u32 apiexcp;
+ u32 apimask;
+
+ /* APIEXCP is cleared when read */
+ apiexcp = __raw_readl(dev_info->vbase + REG_APIEXCP_OFFSET);
+ if ((apiexcp & CPU_EXCP_DETECTED) == 0)
+ return 0;
+
+ apimask = __raw_readl(dev_info->vbase + REG_APIMASK_OFFSET);
+ cpc925_printk(KERN_EMERG, "Processor Interface Fault\n"
+ "Processor Interface register dump:\n");
+ cpc925_printk(KERN_EMERG, "APIMASK 0x%08x\n", apimask);
+ cpc925_printk(KERN_EMERG, "APIEXCP 0x%08x\n", apiexcp);
+
+ edac_device_handle_ue(edac_dev, 0, 0, edac_dev->ctl_name);
+ return 1;
+}
+
/******************** HT Link err device****************************/
/* Enable HyperTransport Link Error detection */
static void cpc925_htlink_init(struct cpc925_dev_info *dev_info)
@@ -704,23 +817,105 @@ static void cpc925_htlink_check(struct edac_device_ctl_info *edac_dev)
edac_device_handle_ce(edac_dev, 0, 0, edac_dev->ctl_name);
}
+static irqreturn_t cpc925_htlink_isr(int irq, void *dev_id)
+{
+ struct edac_device_ctl_info *edac_dev = dev_id;
+ struct cpc925_dev_info *dev_info = edac_dev->pvt_info;
+ u32 brgctrl = __raw_readl(dev_info->vbase + REG_BRGCTRL_OFFSET);
+ u32 linkctrl = __raw_readl(dev_info->vbase + REG_LINKCTRL_OFFSET);
+ u32 errctrl = __raw_readl(dev_info->vbase + REG_ERRCTRL_OFFSET);
+ u32 linkerr = __raw_readl(dev_info->vbase + REG_LINKERR_OFFSET);
+
+ if (!((brgctrl & BRGCTRL_DETSERR) ||
+ (linkctrl & HT_LINKCTRL_DETECTED) ||
+ (errctrl & HT_ERRCTRL_DETECTED) ||
+ (linkerr & HT_LINKERR_DETECTED)))
+ return IRQ_NONE;
+
+ cpc925_htlink_check(edac_dev);
+
+ return IRQ_HANDLED;
+}
+
+/* Private structure for EDAC Memory Controller */
+static struct cpc925_mc_pdata cpc925_mc_private = {
+ /* EDAC MC supports POLL and MCE mode */
+ .op_state = EDAC_OPSTATE_MCE,
+ .mce = cpc925_mc_mce,
+ .mci = NULL,
+};
+
+/*
+ * Private strucutures for common EDAC devices for CPU Error
+ * and Hypertransport Link Error
+ */
static struct cpc925_dev_info cpc925_devs[] = {
{
+ /* CPU Error supports POLL and MCE mode */
+ .op_state = EDAC_OPSTATE_MCE,
.ctl_name = CPC925_CPU_ERR_DEV,
.init = cpc925_cpu_init,
.exit = cpc925_cpu_exit,
.check = cpc925_cpu_check,
+ .mce = cpc925_cpu_mce,
},
{
+ /* Hypertransport Link Error supports POLL and INT mode */
+ .op_state = EDAC_OPSTATE_INT,
.ctl_name = CPC925_HT_LINK_DEV,
.init = cpc925_htlink_init,
.exit = cpc925_htlink_exit,
.check = cpc925_htlink_check,
+ .irq = NO_IRQ,
+ .isr = cpc925_htlink_isr,
},
{0}, /* Terminated by NULL */
};
/*
+ * MCE handler for EDAC CPC925 driver, check memory controller and
+ * Hypertransport hostbridge to claim any possbile MCE instance.
+ */
+static int cpc925_mce_handler(void)
+{
+ struct cpc925_mc_pdata *pdata = &cpc925_mc_private;
+ struct cpc925_dev_info *dev_info;
+ int ret = 0;
+
+ if (pdata->op_state == EDAC_OPSTATE_MCE)
+ if (pdata->mce)
+ ret |= pdata->mce(pdata->mci);
+
+ for (dev_info = &cpc925_devs[0]; dev_info->init; dev_info++) {
+ if (dev_info->op_state == EDAC_OPSTATE_MCE)
+ if (dev_info->mce)
+ ret |= dev_info->mce(dev_info->edac_dev);
+ }
+
+ return ret;
+}
+
+/* Hook CPC925 MCE handler to PowerPC generic MCE handler */
+static void cpc925_mce_handler_setup(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&edac_mce_lock, flags);
+ edac_mce_handler = cpc925_mce_handler;
+ spin_unlock_irqrestore(&edac_mce_lock, flags);
+}
+
+static void cpc925_mce_handler_exit(void)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&edac_mce_lock, flags);
+ if (edac_mce_handler)
+ edac_mce_handler = NULL;
+ spin_unlock_irqrestore(&edac_mce_lock, flags);
+}
+
+/*
* Add CPU Err detection and HyperTransport Link Err detection
* as common "edac_device", they have no corresponding device
* nodes in the Open Firmware DTB and we have to add platform
@@ -730,6 +925,7 @@ static struct cpc925_dev_info cpc925_devs[] = {
static void cpc925_add_edac_devices(void __iomem *vbase)
{
struct cpc925_dev_info *dev_info;
+ int ret = 0;
if (!vbase) {
cpc925_printk(KERN_ERR, "MMIO not established yet\n");
@@ -766,8 +962,36 @@ static void cpc925_add_edac_devices(void __iomem *vbase)
dev_info->edac_dev->mod_name = CPC925_EDAC_MOD_STR;
dev_info->edac_dev->dev_name = dev_name(&dev_info->pdev->dev);
- if (edac_op_state == EDAC_OPSTATE_POLL)
+ if (dev_info->op_state == EDAC_OPSTATE_POLL)
dev_info->edac_dev->edac_check = dev_info->check;
+ else if (dev_info->irq == EDAC_OPSTATE_MCE) {
+ /*
+ * do nothing, MCE handler has been registered
+ * by memory controller.
+ */
+ } else if (dev_info->op_state == EDAC_OPSTATE_INT) {
+ dev_info->irq =
+ edac_get_mpic_irq(MPIC_HWIRQ_INTERNAL_ERROR);
+ if (dev_info->irq == NO_IRQ) {
+ cpc925_printk(KERN_ERR, "%s: failed to get "
+ "virq for %s\n", __func__,
+ dev_info->ctl_name);
+ goto err2;
+ }
+
+ ret = request_irq(dev_info->irq, dev_info->isr,
+ IRQF_SHARED, "[EDAC] CPC925 ",
+ dev_info->edac_dev);
+ if (ret < 0) {
+ cpc925_printk(KERN_INFO, "%s: failed to "
+ "request irq %d for %s\n", __func__,
+ dev_info->irq, dev_info->ctl_name);
+ goto err2;
+ }
+
+ debugf0("%s: Successfully requested irq %d for %s\n",
+ __func__, dev_info->irq, dev_info->ctl_name);
+ }
if (dev_info->init)
dev_info->init(dev_info);
@@ -776,7 +1000,7 @@ static void cpc925_add_edac_devices(void __iomem *vbase)
cpc925_printk(KERN_ERR,
"Unable to add edac device for %s\n",
dev_info->ctl_name);
- goto err2;
+ goto err3;
}
debugf0("%s: Successfully added edac device for %s\n",
@@ -784,9 +1008,13 @@ static void cpc925_add_edac_devices(void __iomem *vbase)
continue;
-err2:
+err3:
if (dev_info->exit)
dev_info->exit(dev_info);
+
+ if (dev_info->op_state == EDAC_OPSTATE_INT)
+ free_irq(dev_info->irq, dev_info->edac_dev);
+err2:
edac_device_free_ctl_info(dev_info->edac_dev);
err1:
platform_device_unregister(dev_info->pdev);
@@ -802,15 +1030,17 @@ static void cpc925_del_edac_devices(void)
struct cpc925_dev_info *dev_info;
for (dev_info = &cpc925_devs[0]; dev_info->init; dev_info++) {
+ if (dev_info->exit)
+ dev_info->exit(dev_info);
+
if (dev_info->edac_dev) {
+ if (dev_info->op_state == EDAC_OPSTATE_INT)
+ free_irq(dev_info->irq, dev_info->edac_dev);
edac_device_del_device(dev_info->edac_dev->dev);
edac_device_free_ctl_info(dev_info->edac_dev);
platform_device_unregister(dev_info->pdev);
}
- if (dev_info->exit)
- dev_info->exit(dev_info);
-
debugf0("%s: Successfully deleted edac device for %s\n",
__func__, dev_info->ctl_name);
}
@@ -900,18 +1130,18 @@ static int __devinit cpc925_probe(struct platform_device *pdev)
}
nr_channels = cpc925_mc_get_channels(vbase);
- mci = edac_mc_alloc(sizeof(struct cpc925_mc_pdata),
- CPC925_NR_CSROWS, nr_channels + 1, edac_mc_idx);
+ mci = edac_mc_alloc(0, CPC925_NR_CSROWS, nr_channels + 1, edac_mc_idx);
if (!mci) {
cpc925_printk(KERN_ERR, "No memory for mem_ctl_info\n");
res = -ENOMEM;
goto err2;
}
- pdata = mci->pvt_info;
+ pdata = mci->pvt_info = &cpc925_mc_private;
pdata->vbase = vbase;
pdata->edac_idx = edac_mc_idx++;
pdata->name = pdev->name;
+ pdata->mci = mci;
mci->dev = &pdev->dev;
platform_set_drvdata(pdev, mci);
@@ -922,15 +1152,16 @@ static int __devinit cpc925_probe(struct platform_device *pdev)
mci->mod_name = CPC925_EDAC_MOD_STR;
mci->mod_ver = CPC925_EDAC_REVISION;
mci->ctl_name = pdev->name;
-
- if (edac_op_state == EDAC_OPSTATE_POLL)
- mci->edac_check = cpc925_mc_check;
-
mci->ctl_page_to_phys = NULL;
mci->scrub_mode = SCRUB_SW_SRC;
mci->set_sdram_scrub_rate = NULL;
mci->get_sdram_scrub_rate = cpc925_get_sdram_scrub_rate;
+ if (pdata->op_state == EDAC_OPSTATE_POLL)
+ mci->edac_check = cpc925_mc_check;
+ else if (pdata->op_state == EDAC_OPSTATE_MCE)
+ cpc925_mce_handler_setup();
+
cpc925_init_csrows(mci);
/* Setup memory controller registers */
@@ -951,6 +1182,10 @@ static int __devinit cpc925_probe(struct platform_device *pdev)
err3:
cpc925_mc_exit(mci);
+
+ if (pdata->op_state == EDAC_OPSTATE_MCE)
+ cpc925_mce_handler_exit();
+
edac_mc_free(mci);
err2:
devm_release_mem_region(&pdev->dev, r->start, r->end-r->start+1);
@@ -963,14 +1198,19 @@ out:
static int cpc925_remove(struct platform_device *pdev)
{
struct mem_ctl_info *mci = platform_get_drvdata(pdev);
+ struct cpc925_mc_pdata *pdata = mci->pvt_info;
/*
* Delete common edac devices before edac mc, because
* the former share the MMIO of the latter.
*/
cpc925_del_edac_devices();
+
cpc925_mc_exit(mci);
+ if (pdata->op_state == EDAC_OPSTATE_MCE)
+ cpc925_mce_handler_exit();
+
edac_mc_del_mc(&pdev->dev);
edac_mc_free(mci);
@@ -981,7 +1221,7 @@ static struct platform_driver cpc925_edac_driver = {
.probe = cpc925_probe,
.remove = cpc925_remove,
.driver = {
- .name = "cpc925_edac",
+ .name = "cpc925_edac",
}
};
@@ -992,9 +1232,6 @@ static int __init cpc925_edac_init(void)
printk(KERN_INFO "IBM CPC925 EDAC driver " CPC925_EDAC_REVISION "\n");
printk(KERN_INFO "\t(c) 2008 Wind River Systems, Inc\n");
- /* Only support POLL mode so far */
- edac_op_state = EDAC_OPSTATE_POLL;
-
ret = platform_driver_register(&cpc925_edac_driver);
if (ret) {
printk(KERN_WARNING "Failed to register %s\n",
diff --git a/drivers/edac/edac_stub.c b/drivers/edac/edac_stub.c
index 20b428a..d2814d0 100644
--- a/drivers/edac/edac_stub.c
+++ b/drivers/edac/edac_stub.c
@@ -44,3 +44,9 @@ void edac_atomic_assert_error(void)
edac_err_assert++;
}
EXPORT_SYMBOL_GPL(edac_atomic_assert_error);
+
+int (*edac_mce_handler)(void) = NULL;
+EXPORT_SYMBOL_GPL(edac_mce_handler);
+
+DEFINE_SPINLOCK(edac_mce_lock);
+EXPORT_SYMBOL_GPL(edac_mce_lock);
diff --git a/include/linux/edac.h b/include/linux/edac.h
index c122d22..da2dc20 100644
--- a/include/linux/edac.h
+++ b/include/linux/edac.h
@@ -12,12 +12,14 @@
#ifndef _LINUX_EDAC_H_
#define _LINUX_EDAC_H_
+#include <linux/spinlock.h>
#include <asm/atomic.h>
#define EDAC_OPSTATE_INVAL -1
#define EDAC_OPSTATE_POLL 0
#define EDAC_OPSTATE_NMI 1
#define EDAC_OPSTATE_INT 2
+#define EDAC_OPSTATE_MCE 3
extern int edac_op_state;
extern int edac_err_assert;
@@ -26,11 +28,15 @@ extern atomic_t edac_handlers;
extern int edac_handler_set(void);
extern void edac_atomic_assert_error(void);
+extern int (*edac_mce_handler)(void);
+extern spinlock_t edac_mce_lock;
+
static inline void opstate_init(void)
{
switch (edac_op_state) {
case EDAC_OPSTATE_POLL:
case EDAC_OPSTATE_NMI:
+ case EDAC_OPSTATE_MCE:
break;
default:
edac_op_state = EDAC_OPSTATE_POLL;
--
1.5.6.2
^ permalink raw reply related
* the question about the DDR1_Rcomp code for INTEL IXP4XX CPU?
From: guojin02 @ 2009-05-18 9:50 UTC (permalink / raw)
To: u-boot, linuxppc-dev
[-- Attachment #1: Type: text/plain, Size: 2880 bytes --]
Hi,every one,
I want to drive the Rcomp circuit for ixp460, because I found that the ixp460 could not power up on low temperature(-40 degree). I want to add those code on start.s, follow is the instruction from ixp460 developer's manual. Does anyone can help me realize those code from No.4 to No.6?
Instrction:
1. The MCU applies the clock DDRI_CK[2:0] at power up along with system power
(clock frequency unknown).
2. The MCU must stabilize DDRI_CK[2:0] within 100 μs after power stabilizes.
3. The MCU holds the following control inputs inactive:
DDRI_RAS_N, DDRI_CAS_N, DDRI_WE_N, DDRI_CS_N[1:0]
The MCU then places all of the following data outputs and strobes in the High-Z
state:
DDRI_DQS[4:0], DDRI_DQ[31:0], DDR_CB[7:0]
4. The MCU then performs sixteen RCOMP calibration cycles which take an additional
34 ms. Software MUST wait until this time has elapsed before continuing with DDRI
SDRAM initialization.
5. Software overwrites the default value of register DDR_RCOMP_CSR3 (Hex Offset
Address = 0x0CC00 F570) with a new value of 0x0000 1000.
6. Software overwrites the default value of register DDR_DRIVE3 (Hex Offset Address
= 0x0CC00 F5AC) with a new value of 0x0002 08F0.
7. Software disables the refresh counter by setting the RFR to zero.
8. Software issues one NOP cycle after the 200 us device deselect. A NOP is
accomplished by setting the SDIR to 00112. The MCU asserts DDRI_CKE[1:0] with
the NOP.
9. Software issues a precharge-all command to the DDRI SDRAM interface by setting
the SDIR to 00102.
10. Software issues an extended-mode-register-set command to enable the DLL by
writing 01002 to the SDIR. The MCU supports the following DDRI SDRAM mode
parameters:
11. After waiting Tmrd cycles, software issues a mode-register-set command by writing
00012 to the SDIR to program the DDRI SDRAM parameters and to reset the DLL.
The MCU supports the following DDRI SDRAM mode parameters:
12. After waiting Tmrd cycles, software issues a precharge-all command to the DDRI
SDRAM interface by setting the SDIR to 00102.
13. After waiting Trp cycles, software provides two auto-refresh cycles. An auto-refresh
cycle is accomplished by setting the SDIR to 01102. Software must ensure at least
Trfc cycles between each auto-refresh command.
14. Following the second auto-refresh cycle, software must wait Trfc cycles. Then,
software issues a mode-register-set command by writing to the SDIR to program the
DDRI SDRAM parameters without resetting the DLL by writing 00002 to the SDIR.
15. The MCU may issue a row-activate command Tmrd cycles after the mode-register-set
command.
16. Software re-enables the refresh counter by setting the RFR to the required value.
The waveform in Figure 109 illustrates the DDRI SDRAM initialization sequence.
2009-05-18
郭 劲
13810607876
010-62771694
[-- Attachment #2: Type: text/html, Size: 6424 bytes --]
^ permalink raw reply
* Re: Linuxppc-dev Digest, Vol 57, Issue 42
From: Hongjun Chen @ 2009-05-18 7:53 UTC (permalink / raw)
To: wd; +Cc: linuxppc-dev@ozlabs.org
In-Reply-To: <mailman.7208.1241640931.26545.linuxppc-dev@ozlabs.org>
Why should you reinvent wheel for MPC512x DMA driver? We have a ready DMA driver, which has been used by MPC5121 AC97, VIU, ATA etc.
B.R,
Hongjun Chen
2009-05-18
发件人: linuxppc-dev-request@ozlabs.org
发送时间: 2009-05-07 04:38:43
收件人: linuxppc-dev@ozlabs.org
抄送:
主题: Linuxppc-dev Digest, Vol 57, Issue 42
Send Linuxppc-dev mailing list submissions to
linuxppc-dev@ozlabs.org
To subscribe or unsubscribe via the World Wide Web, visit
https://ozlabs.org/mailman/listinfo/linuxppc-dev
or, via email, send a message with subject or body 'help' to
linuxppc-dev-request@ozlabs.org
You can reach the person managing the list at
linuxppc-dev-owner@ozlabs.org
When replying, please edit your Subject line so it is more specific
than "Re: Contents of Linuxppc-dev digest..."
Today's Topics:
1. [PATCH 08/12] mpc5121: Added I2C support. (Wolfgang Denk)
2. [PATCH 05/12] mpc5121ads: Added Reset Module node to DTS.
(Wolfgang Denk)
3. [PATCH 10/12] mpc5121: Add MPC5121 Real time clock driver.
(Wolfgang Denk)
4. [PATCH 11/12] mpc5121: Added MPC512x DMA driver. (Wolfgang Denk)
----------------------------------------------------------------------
Message: 1
Date: Wed, 6 May 2009 22:15:15 +0200
From: Wolfgang Denk <wd@denx.de >
Subject: [PATCH 08/12] mpc5121: Added I2C support.
To: linuxppc-dev@ozlabs.org
Cc: linux-i2c@vger.kernel.org, Piotr Ziecik <kosmo@semihalf.com >,
Wolfgang Denk <wd@denx.de >
Message-ID: <1241640919-4650-9-git-send-email-wd@denx.de >
From: Piotr Ziecik <kosmo@semihalf.com >
- Enabled I2C interrupts on MPC5121.
- Updated Kconfig for i2c-mpc driver.
Signed-off-by: Piotr Ziecik <kosmo@semihalf.com >
Signed-off-by: Wolfgang Denk <wd@denx.de >
Cc: <linux-i2c@vger.kernel.org >
Cc: Grant Likely <grant.likely@secretlab.ca >
Cc: John Rigby <jcrigby@gmail.com >
---
arch/powerpc/platforms/512x/mpc5121_ads.c | 2 ++
arch/powerpc/platforms/512x/mpc512x.h | 1 +
arch/powerpc/platforms/512x/mpc512x_shared.c | 24 ++++++++++++++++++++++++
drivers/i2c/busses/Kconfig | 9 +++++----
4 files changed, 32 insertions(+), 4 deletions(-)
diff --git a/arch/powerpc/platforms/512x/mpc5121_ads.c b/arch/powerpc/platforms/512x/mpc5121_ads.c
index 441abc4..a8976b4 100644
--- a/arch/powerpc/platforms/512x/mpc5121_ads.c
+++ b/arch/powerpc/platforms/512x/mpc5121_ads.c
@@ -42,6 +42,8 @@ static void __init mpc5121_ads_setup_arch(void)
for_each_compatible_node(np, "pci", "fsl,mpc5121-pci")
mpc83xx_add_bridge(np);
#endif
+
+ mpc512x_init_i2c();
}
static void __init mpc5121_ads_init_IRQ(void)
diff --git a/arch/powerpc/platforms/512x/mpc512x.h b/arch/powerpc/platforms/512x/mpc512x.h
index 9c03693..f4db8a7 100644
--- a/arch/powerpc/platforms/512x/mpc512x.h
+++ b/arch/powerpc/platforms/512x/mpc512x.h
@@ -13,5 +13,6 @@
#define __MPC512X_H__
extern unsigned long mpc512x_find_ips_freq(struct device_node *node);
extern void __init mpc512x_init_IRQ(void);
+extern void __init mpc512x_init_i2c(void);
void __init mpc512x_declare_of_platform_devices(void);
#endif /* __MPC512X_H__ */
diff --git a/arch/powerpc/platforms/512x/mpc512x_shared.c b/arch/powerpc/platforms/512x/mpc512x_shared.c
index 7135d89..b776e45 100644
--- a/arch/powerpc/platforms/512x/mpc512x_shared.c
+++ b/arch/powerpc/platforms/512x/mpc512x_shared.c
@@ -65,6 +65,30 @@ void __init mpc512x_init_IRQ(void)
ipic_set_default_priority();
}
+void __init mpc512x_init_i2c(void)
+{
+ struct device_node *np;
+ void __iomem *i2cctl;
+
+ /* Enable I2C interrupts */
+ np = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-i2c-ctrl");
+ if (np) {
+ i2cctl = of_iomap(np, 0);
+ if (i2cctl) {
+ /*
+ * Set interrupt enable bits:
+ * - I2C-0: bit 24,
+ * - I2C-1: bit 26,
+ * - I2C-2: bit 28.
+ */
+ out_be32(i2cctl, 0x15000000);
+ iounmap(i2cctl);
+ }
+
+ of_node_put(np);
+ }
+}
+
/*
* Nodes to do bus probe on, soc and localbus
*/
diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig
index a48c8ae..57ed637 100644
--- a/drivers/i2c/busses/Kconfig
+++ b/drivers/i2c/busses/Kconfig
@@ -391,13 +391,14 @@ config I2C_IXP2000
instead.
config I2C_MPC
- tristate "MPC107/824x/85xx/52xx/86xx"
+ tristate "MPC107/824x/85xx/512x/52xx/86xx"
depends on PPC32
help
If you say yes to this option, support will be included for the
- built-in I2C interface on the MPC107/Tsi107/MPC8240/MPC8245 and
- MPC85xx/MPC8641 family processors. The driver may also work on 52xx
- family processors, though interrupts are known not to work.
+ built-in I2C interface on the MPC107/Tsi107/MPC8240/MPC8245,
+ MPC85xx/MPC8641 and MPC512x family processors. The driver may
+ also work on 52xx family processors, though interrupts are known
+ not to work.
This driver can also be built as a module. If so, the module
will be called i2c-mpc.
--
1.6.0.6
------------------------------
Message: 2
Date: Wed, 6 May 2009 22:15:12 +0200
From: Wolfgang Denk <wd@denx.de >
Subject: [PATCH 05/12] mpc5121ads: Added Reset Module node to DTS.
To: linuxppc-dev@ozlabs.org
Cc: Piotr Ziecik <kosmo@semihalf.com >, Wolfgang Denk <wd@denx.de >
Message-ID: <1241640919-4650-6-git-send-email-wd@denx.de >
From: Piotr Ziecik <kosmo@semihalf.com >
Signed-off-by: Piotr Ziecik <kosmo@semihalf.com >
Signed-off-by: Wolfgang Denk <wd@denx.de >
Cc: Grant Likely <grant.likely@secretlab.ca >
Cc: John Rigby <jcrigby@gmail.com >
---
arch/powerpc/boot/dts/mpc5121ads.dts | 5 +++++
1 files changed, 5 insertions(+), 0 deletions(-)
diff --git a/arch/powerpc/boot/dts/mpc5121ads.dts b/arch/powerpc/boot/dts/mpc5121ads.dts
index c2b8dbf..1b83a9d 100644
--- a/arch/powerpc/boot/dts/mpc5121ads.dts
+++ b/arch/powerpc/boot/dts/mpc5121ads.dts
@@ -166,6 +166,11 @@
interrupt-parent = < &ipic >;
};
+ reset@e00 { // Reset module
+ compatible = "fsl,mpc5121-reset";
+ reg = <0xe00 0x100 >;
+ };
+
clock@f00 { // Clock control
compatible = "fsl,mpc5121-clock";
reg = <0xf00 0x100 >;
--
1.6.0.6
------------------------------
Message: 3
Date: Wed, 6 May 2009 22:15:17 +0200
From: Wolfgang Denk <wd@denx.de >
Subject: [PATCH 10/12] mpc5121: Add MPC5121 Real time clock driver.
To: linuxppc-dev@ozlabs.org
Cc: Piotr Ziecik <kosmo@semihalf.com >, rtc-linux@googlegroups.com,
John Rigby <jrigby@freescale.com >, Wolfgang Denk <wd@denx.de >
Message-ID: <1241640919-4650-11-git-send-email-wd@denx.de >
From: John Rigby <jrigby@freescale.com >
Based on Domen Puncer's rtc driver for 5200 posted to
the ppclinux mailing list:
http://patchwork.ozlabs.org/linuxppc-embedded/patch?id=11675
but never commited anywhere.
Changes to Domen's original:
Changed filenames/routine names from mpc5200* to mpc5121*
Changed match to only care about compatible and use "fsl,"
convention for compatible.
Make alarms more sane by dealing with lack of second alarm resolution.
Deal with the fact that most of the 5121 rtc registers are not persistent
across a reset even with a battery attached:
Use actual_time register for time keeping
and target_time register as an offset to linux time
The target_time register would normally be used for hibernation
but hibernation does not work on current silicon
Signed-off-by: John Rigby <jrigby@freescale.com >
Signed-off-by: Piotr Ziecik <kosmo@semihalf.com >
Signed-off-by: Wolfgang Denk <wd@denx.de >
Cc: <rtc-linux@googlegroups.com >
Cc: Grant Likely <grant.likely@secretlab.ca >
Cc: John Rigby <jcrigby@gmail.com >
---
drivers/rtc/Kconfig | 10 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-mpc5121.c | 408 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 419 insertions(+), 0 deletions(-)
create mode 100644 drivers/rtc/rtc-mpc5121.c
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 4e9851f..900d5b8 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -750,4 +750,14 @@ config RTC_DRV_PS3
This driver can also be built as a module. If so, the module
will be called rtc-ps3.
+config RTC_DRV_MPC5121
+ tristate "Freescale MPC5121 built-in RTC"
+ depends on RTC_CLASS
+ help
+ If you say yes here you will get support for the
+ built-in RTC MPC5121.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-mpc5121.
+
endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 6c0639a..8c6d6a7 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_RTC_DRV_STARFIRE) += rtc-starfire.o
obj-$(CONFIG_RTC_DRV_MAX6900) += rtc-max6900.o
obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o
obj-$(CONFIG_RTC_DRV_MV) += rtc-mv.o
+obj-$(CONFIG_RTC_DRV_MPC5121) += rtc-mpc5121.o
obj-$(CONFIG_RTC_DRV_OMAP) += rtc-omap.o
obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o
obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
diff --git a/drivers/rtc/rtc-mpc5121.c b/drivers/rtc/rtc-mpc5121.c
new file mode 100644
index 0000000..63460cb
--- /dev/null
+++ b/drivers/rtc/rtc-mpc5121.c
@@ -0,0 +1,408 @@
+/*
+ * Real-time clock driver for MPC5121
+ *
+ * Copyright 2007, Domen Puncer <domen.puncer@telargo.com >
+ * Copyright 2008, Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * 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.
+ */
+
+/*
+ * History:
+ *
+ * Based on mpc5200_rtc.c written by Domen Puncer <domen.puncer@telargo.com >
+ * posted to linuxppc-embedded mailing list:
+ * http://patchwork.ozlabs.org/linuxppc-embedded/patch?id=11675
+ * but never committed to any public tree.
+ *
+ * Author: John Rigby <jrigby@freescale.com >
+ * Converted to 5121 rtc driver.
+ *
+ * Make alarms more sane by dealing with lack of second alarm resolution.
+ *
+ * Use actual_time time register for time keeping since it is persistent
+ * and the normal rtc registers are not. Use target_time register as an
+ * offset to linux time.
+ *
+ */
+
+#include <linux/module.h >
+#include <linux/rtc.h >
+#include <linux/of_device.h >
+#include <linux/of_platform.h >
+#include <linux/io.h >
+
+struct mpc5121_rtc_regs {
+ u8 set_time; /* RTC + 0x00 */
+ u8 hour_set; /* RTC + 0x01 */
+ u8 minute_set; /* RTC + 0x02 */
+ u8 second_set; /* RTC + 0x03 */
+
+ u8 set_date; /* RTC + 0x04 */
+ u8 month_set; /* RTC + 0x05 */
+ u8 weekday_set; /* RTC + 0x06 */
+ u8 date_set; /* RTC + 0x07 */
+
+ u8 write_sw; /* RTC + 0x08 */
+ u8 sw_set; /* RTC + 0x09 */
+ u16 year_set; /* RTC + 0x0a */
+
+ u8 alm_enable; /* RTC + 0x0c */
+ u8 alm_hour_set; /* RTC + 0x0d */
+ u8 alm_min_set; /* RTC + 0x0e */
+ u8 int_enable; /* RTC + 0x0f */
+
+ u8 reserved1;
+ u8 hour; /* RTC + 0x11 */
+ u8 minute; /* RTC + 0x12 */
+ u8 second; /* RTC + 0x13 */
+
+ u8 month; /* RTC + 0x14 */
+ u8 wday_mday; /* RTC + 0x15 */
+ u16 year; /* RTC + 0x16 */
+
+ u8 int_alm; /* RTC + 0x18 */
+ u8 int_sw; /* RTC + 0x19 */
+ u8 alm_status; /* RTC + 0x1a */
+ u8 sw_minute; /* RTC + 0x1b */
+
+ u8 bus_error_1; /* RTC + 0x1c */
+ u8 int_day; /* RTC + 0x1d */
+ u8 int_min; /* RTC + 0x1e */
+ u8 int_sec; /* RTC + 0x1f */
+
+ /*
+ * target_time:
+ * intended to be used for hibernation but hibernation
+ * does not work on silicon rev 1.5 so use it for non-volatile
+ * storage of offset between the actual_time register and linux
+ * time
+ */
+ u32 target_time; /* RTC + 0x20 */
+ /*
+ * actual_time:
+ * readonly time since VBAT_RTC was last connected
+ */
+ u32 actual_time; /* RTC + 0x24 */
+ u32 keep_alive; /* RTC + 0x28 */
+};
+
+struct mpc5121_rtc_data {
+ unsigned irq;
+ unsigned irq_periodic;
+ struct mpc5121_rtc_regs __iomem *regs;
+ struct rtc_device *rtc;
+ struct rtc_wkalrm wkalarm;
+};
+
+/*
+ * Update second/minute/hour registers.
+ *
+ * This is just so alarm will work.
+ */
+static void mpc5121_rtc_update_smh(struct mpc5121_rtc_regs __iomem *regs,
+ struct rtc_time *tm)
+{
+ out_8(®s- >second_set, tm- >tm_sec);
+ out_8(®s- >minute_set, tm- >tm_min);
+ out_8(®s- >hour_set, tm- >tm_hour);
+
+ /* set time sequence */
+ out_8(®s- >set_time, 0x1);
+ out_8(®s- >set_time, 0x3);
+ out_8(®s- >set_time, 0x1);
+ out_8(®s- >set_time, 0x0);
+}
+
+static int mpc5121_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+ unsigned long now;
+
+ /*
+ * linux time is actual_time plus the offset saved in target_time
+ */
+ now = in_be32(®s- >actual_time) + in_be32(®s- >target_time);
+
+ rtc_time_to_tm(now, tm);
+
+ /*
+ * update second minute hour registers
+ * so alarms will work
+ */
+ mpc5121_rtc_update_smh(regs, tm);
+
+ return 0;
+}
+
+static int mpc5121_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+ int ret;
+ unsigned long now;
+
+
+ /*
+ * The actual_time register is read only so we write the offset
+ * between it and linux time to the target_time register.
+ */
+ ret = rtc_tm_to_time(tm, &now);
+ if (ret == 0)
+ out_be32(®s- >target_time, now - in_be32(®s- >actual_time));
+
+ /*
+ * update second minute hour registers
+ * so alarms will work
+ */
+ mpc5121_rtc_update_smh(regs, tm);
+
+ return 0;
+}
+
+static int mpc5121_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+
+ *alarm = rtc- >wkalarm;
+
+ alarm- >pending = in_8(®s- >alm_status);
+
+ return 0;
+}
+
+static int mpc5121_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+
+ /*
+ * the alarm has no seconds so deal with it
+ */
+ if (alarm- >time.tm_sec) {
+ alarm- >time.tm_sec = 0;
+ alarm- >time.tm_min++;
+ if (alarm- >time.tm_min >= 60) {
+ alarm- >time.tm_min = 0;
+ alarm- >time.tm_hour++;
+ if (alarm- >time.tm_hour >= 24)
+ alarm- >time.tm_hour = 0;
+ }
+ }
+
+ alarm- >time.tm_mday = -1;
+ alarm- >time.tm_mon = -1;
+ alarm- >time.tm_year = -1;
+
+ out_8(®s- >alm_min_set, alarm- >time.tm_min);
+ out_8(®s- >alm_hour_set, alarm- >time.tm_hour);
+
+ out_8(®s- >alm_enable, alarm- >enabled);
+
+ rtc- >wkalarm = *alarm;
+ return 0;
+}
+
+static irqreturn_t mpc5121_rtc_handler(int irq, void *dev)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata((struct device *)dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+
+ if (in_8(®s- >int_alm)) {
+ /* acknowledge and clear status */
+ out_8(®s- >int_alm, 1);
+ out_8(®s- >alm_status, 1);
+
+ rtc_update_irq(rtc- >rtc, 1, RTC_IRQF | RTC_AF);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t mpc5121_rtc_handler_upd(int irq, void *dev)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata((struct device *)dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+
+ if (in_8(®s- >int_sec) && (in_8(®s- >int_enable) & 0x1)) {
+ /* acknowledge */
+ out_8(®s- >int_sec, 1);
+
+ rtc_update_irq(rtc- >rtc, 1, RTC_IRQF | RTC_UF);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static int mpc5121_rtc_ioctl(struct device *dev, unsigned int cmd,
+ unsigned long arg)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata(dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+
+ switch (cmd) {
+ /* alarm interrupt */
+ case RTC_AIE_ON:
+ out_8(®s- >alm_enable, 1);
+ rtc- >wkalarm.enabled = 1;
+ break;
+ case RTC_AIE_OFF:
+ out_8(®s- >alm_enable, 0);
+ rtc- >wkalarm.enabled = 0;
+ break;
+
+ /* update interrupt */
+ case RTC_UIE_ON:
+ out_8(®s- >int_enable,
+ (in_8(®s- >int_enable) & ~0x8) | 0x1);
+ break;
+ case RTC_UIE_OFF:
+ out_8(®s- >int_enable, in_8(®s- >int_enable) & ~0x1);
+ break;
+
+ /* no periodic interrupts */
+ case RTC_IRQP_READ:
+ case RTC_IRQP_SET:
+ return -ENOTTY;
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+ return 0;
+}
+
+static const struct rtc_class_ops mpc5121_rtc_ops = {
+ .read_time = mpc5121_rtc_read_time,
+ .set_time = mpc5121_rtc_set_time,
+ .read_alarm = mpc5121_rtc_read_alarm,
+ .set_alarm = mpc5121_rtc_set_alarm,
+ .ioctl = mpc5121_rtc_ioctl,
+};
+
+static int __devinit mpc5121_rtc_probe(struct of_device *op,
+ const struct of_device_id *match)
+{
+ struct mpc5121_rtc_data *rtc;
+ int err = 0;
+ u32 ka;
+
+ rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
+ if (!rtc) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ rtc- >regs = of_iomap(op- >node, 0);
+
+ if (!rtc- >regs) {
+ printk(KERN_ERR "%s: couldn't map io space\n", __func__);
+ err = -ENOSYS;
+ goto out_free;
+ }
+
+ device_init_wakeup(&op- >dev, 1);
+
+ rtc- >rtc = rtc_device_register("mpc5121-rtc", &op- >dev,
+ &mpc5121_rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc- >rtc)) {
+ err = PTR_ERR(rtc- >rtc);
+ goto out_unmap;
+ }
+
+ dev_set_drvdata(&op- >dev, rtc);
+
+ rtc- >irq = irq_of_parse_and_map(op- >node, 1);
+ err = request_irq(rtc- >irq, mpc5121_rtc_handler, IRQF_DISABLED,
+ "mpc5121-rtc", &op- >dev);
+ if (err) {
+ printk(KERN_ERR "%s: could not request irq: %i\n",
+ __func__, rtc- >irq);
+ goto out_dispose;
+ }
+
+ rtc- >irq_periodic = irq_of_parse_and_map(op- >node, 0);
+ err = request_irq(rtc- >irq_periodic, mpc5121_rtc_handler_upd,
+ IRQF_DISABLED, "mpc5121-rtc_upd", &op- >dev);
+ if (err) {
+ printk(KERN_ERR "%s: could not request irq: %i\n",
+ __func__, rtc- >irq_periodic);
+ goto out_dispose2;
+ }
+
+ ka = in_be32(&rtc- >regs- >keep_alive);
+ if (ka & 0x02) {
+ printk(KERN_WARNING
+ "mpc5121-rtc: Battery or oscillator failure!\n");
+ out_be32(&rtc- >regs- >keep_alive, ka);
+ }
+
+ goto out;
+
+out_dispose2:
+ irq_dispose_mapping(rtc- >irq_periodic);
+ free_irq(rtc- >irq, &op- >dev);
+out_dispose:
+ irq_dispose_mapping(rtc- >irq);
+out_unmap:
+ iounmap(rtc- >regs);
+out_free:
+ kfree(rtc);
+out:
+ return err;
+}
+
+static int __devexit mpc5121_rtc_remove(struct of_device *op)
+{
+ struct mpc5121_rtc_data *rtc = dev_get_drvdata(&op- >dev);
+ struct mpc5121_rtc_regs __iomem *regs = rtc- >regs;
+
+ /* disable interrupt, so there are no nasty surprises */
+ out_8(®s- >alm_enable, 0);
+ out_8(®s- >int_enable, in_8(®s- >int_enable) & ~0x1);
+
+ rtc_device_unregister(rtc- >rtc);
+ iounmap(rtc- >regs);
+ free_irq(rtc- >irq, &op- >dev);
+ free_irq(rtc- >irq_periodic, &op- >dev);
+ irq_dispose_mapping(rtc- >irq);
+ irq_dispose_mapping(rtc- >irq_periodic);
+ dev_set_drvdata(&op- >dev, NULL);
+ kfree(rtc);
+
+ return 0;
+}
+
+static struct of_device_id mpc5121_rtc_match[] = {
+ { .compatible = "fsl,mpc5121-rtc", },
+ {},
+};
+
+static struct of_platform_driver mpc5121_rtc_driver = {
+ .owner = THIS_MODULE,
+ .name = "mpc5121-rtc",
+ .match_table = mpc5121_rtc_match,
+ .probe = mpc5121_rtc_probe,
+ .remove = mpc5121_rtc_remove,
+};
+
+static int __init mpc5121_rtc_init(void)
+{
+ return of_register_platform_driver(&mpc5121_rtc_driver);
+}
+
+static void __exit mpc5121_rtc_exit(void)
+{
+ of_unregister_platform_driver(&mpc5121_rtc_driver);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("John Rigby <jrigby@freescale.com >");
+
+module_init(mpc5121_rtc_init);
+module_exit(mpc5121_rtc_exit);
--
1.6.0.6
------------------------------
Message: 4
Date: Wed, 6 May 2009 22:15:18 +0200
From: Wolfgang Denk <wd@denx.de >
Subject: [PATCH 11/12] mpc5121: Added MPC512x DMA driver.
To: linuxppc-dev@ozlabs.org
Cc: Piotr Ziecik <kosmo@semihalf.com >, Wolfgang Denk <wd@denx.de >
Message-ID: <1241640919-4650-12-git-send-email-wd@denx.de >
From: Piotr Ziecik <kosmo@semihalf.com >
This patch adds initial version of MPC512x DMA driver.
Only memory to memory transfers are currenly supported.
Signed-off-by: Piotr Ziecik <kosmo@semihalf.com >
Signed-off-by: Wolfgang Denk <wd@denx.de >
Cc: Grant Likely <grant.likely@secretlab.ca >
Cc: John Rigby <jcrigby@gmail.com >
---
arch/powerpc/boot/dts/mpc5121ads.dts | 2 +-
arch/powerpc/platforms/512x/mpc512x_shared.c | 1 +
drivers/dma/Kconfig | 7 +
drivers/dma/Makefile | 1 +
drivers/dma/mpc512x_dma.c | 642 ++++++++++++++++++++++++++
drivers/dma/mpc512x_dma.h | 192 ++++++++
6 files changed, 844 insertions(+), 1 deletions(-)
create mode 100644 drivers/dma/mpc512x_dma.c
create mode 100644 drivers/dma/mpc512x_dma.h
diff --git a/arch/powerpc/boot/dts/mpc5121ads.dts b/arch/powerpc/boot/dts/mpc5121ads.dts
index c2d9de9..e7f0e09 100644
--- a/arch/powerpc/boot/dts/mpc5121ads.dts
+++ b/arch/powerpc/boot/dts/mpc5121ads.dts
@@ -373,7 +373,7 @@
};
dma@14000 {
- compatible = "fsl,mpc5121-dma2";
+ compatible = "fsl,mpc512x-dma";
reg = <0x14000 0x1800 >;
interrupts = <65 0x8 >;
interrupt-parent = < &ipic >;
diff --git a/arch/powerpc/platforms/512x/mpc512x_shared.c b/arch/powerpc/platforms/512x/mpc512x_shared.c
index b776e45..135fd6b 100644
--- a/arch/powerpc/platforms/512x/mpc512x_shared.c
+++ b/arch/powerpc/platforms/512x/mpc512x_shared.c
@@ -95,6 +95,7 @@ void __init mpc512x_init_i2c(void)
static struct of_device_id __initdata of_bus_ids[] = {
{ .compatible = "fsl,mpc5121-immr", },
{ .compatible = "fsl,mpc5121-localbus", },
+ { .compatible = "fsl,mpc5121-dma", },
{ .compatible = "fsl,mpc5121-nfc", },
{},
};
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 3b3c01b..e734a7a 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -55,6 +55,13 @@ config FSL_DMA
The Elo is the DMA controller on some 82xx and 83xx parts, and the
Elo Plus is the DMA controller on 85xx and 86xx parts.
+config MPC512X_DMA
+ tristate "Freescale MPC512x built-in DMA engine support"
+ depends on PPC_MPC512x
+ select DMA_ENGINE
+ ---help---
+ Enable support for the Freescale MPC512x built-in DMA engine.
+
config MV_XOR
bool "Marvell XOR engine support"
depends on PLAT_ORION
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 2e5dc96..f02806c 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -5,6 +5,7 @@ obj-$(CONFIG_INTEL_IOATDMA) += ioatdma.o
ioatdma-objs := ioat.o ioat_dma.o ioat_dca.o
obj-$(CONFIG_INTEL_IOP_ADMA) += iop-adma.o
obj-$(CONFIG_FSL_DMA) += fsldma.o
+obj-$(CONFIG_MPC512X_DMA) += mpc512x_dma.o
obj-$(CONFIG_MV_XOR) += mv_xor.o
obj-$(CONFIG_DW_DMAC) += dw_dmac.o
obj-$(CONFIG_MX3_IPU) += ipu/
diff --git a/drivers/dma/mpc512x_dma.c b/drivers/dma/mpc512x_dma.c
new file mode 100644
index 0000000..1aac897
--- /dev/null
+++ b/drivers/dma/mpc512x_dma.c
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) Semihalf 2009
+ *
+ * Written by: Piotr Ziecik <kosmo@semihalf.com >
+ *
+ * Approved as OSADL project by a majority of OSADL members and funded
+ * by OSADL membership fees in 2009; for details see www.osadl.org.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * The full GNU General Public License is included in this distribution in the
+ * file called COPYING.
+ */
+
+/*
+ * This is initial version of MPC5121 DMA driver. Only memory to memory
+ * transfers are supported (tested using dmatest module).
+ */
+
+#include <linux/module.h >
+#include <linux/dmaengine.h >
+#include <linux/dma-mapping.h >
+#include <linux/interrupt.h >
+#include <linux/io.h >
+#include <linux/of_device.h >
+#include <linux/of_platform.h >
+
+#include <linux/random.h >
+
+#include "mpc512x_dma.h"
+
+#define DRV_NAME "mpc512x_dma"
+
+/* Convert struct dma_chan to struct mpc_dma_chan */
+static inline struct mpc_dma_chan *dma_chan_to_mpc_dma_chan(struct dma_chan *c)
+{
+ return container_of(c, struct mpc_dma_chan, chan);
+}
+
+/* Convert struct dma_chan to struct mpc_dma */
+static inline struct mpc_dma *dma_chan_to_mpc_dma(struct dma_chan *c)
+{
+ struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(c);
+ return container_of(mchan, struct mpc_dma, channels[c- >chan_id]);
+}
+
+/*
+ * Execute all queued DMA descriptors.
+ *
+ * Following requirements must be met while calling mpc_dma_execute():
+ * a) mchan- >lock is acquired,
+ * b) mchan- >active list is empty,
+ * c) mchan- >queued list contains at least one entry.
+ */
+static void mpc_dma_execute(struct mpc_dma_chan *mchan)
+{
+ struct mpc_dma *mdma = dma_chan_to_mpc_dma(&mchan- >chan);
+ struct mpc_dma_desc *first = NULL;
+ struct mpc_dma_desc *prev = NULL;
+ struct mpc_dma_desc *mdesc;
+ int cid = mchan- >chan.chan_id;
+
+ /* Move all queued descriptors to active list */
+ list_splice_tail_init(&mchan- >queued, &mchan- >active);
+
+ /* Chain descriptors into one transaction */
+ list_for_each_entry(mdesc, &mchan- >active, node) {
+ if (!first)
+ first = mdesc;
+
+ if (!prev) {
+ prev = mdesc;
+ continue;
+ }
+
+ prev- >tcd- >dlast_sga = mdesc- >tcd_paddr;
+ prev- >tcd- >e_sg = 1;
+ mdesc- >tcd- >start = 1;
+
+ prev = mdesc;
+ }
+
+ prev- >tcd- >start = 0;
+ prev- >tcd- >int_maj = 1;
+
+ /* Send first descriptor in chain into hardware */
+ memcpy_toio(&mdma- >tcd[cid], first- >tcd, sizeof(struct mpc_dma_tcd));
+ out_8(&mdma- >regs- >dmassrt, cid);
+}
+
+/* Handle interrupt on one half of DMA controller (32 channels) */
+static void mpc_dma_irq_process(struct mpc_dma *mdma, u32 is, u32 es, int off)
+{
+ struct mpc_dma_chan *mchan;
+ struct mpc_dma_desc *mdesc;
+ u32 status = is | es;
+ int ch;
+
+ while ((ch = fls(status) - 1) >= 0) {
+ status &= ~(1 < < ch);
+ mchan = &mdma- >channels[ch + off];
+
+ spin_lock(&mchan- >lock);
+
+ /* Check error status */
+ if (es & (1 < < ch))
+ list_for_each_entry(mdesc, &mchan- >active, node)
+ mdesc- >error = -EIO;
+
+ /* Execute queued descriptors */
+ list_splice_tail_init(&mchan- >active, &mchan- >completed);
+ if (!list_empty(&mchan- >queued))
+ mpc_dma_execute(mchan);
+
+ spin_unlock(&mchan- >lock);
+ }
+}
+
+/* Interrupt handler */
+static irqreturn_t mpc_dma_irq(int irq, void *data)
+{
+ struct mpc_dma *mdma = data;
+ uint es;
+
+ /* Save error status register */
+ es = in_be32(&mdma- >regs- >dmaes);
+ spin_lock(&mdma- >error_status_lock);
+ if ((es & MPC_DMA_DMAES_VLD) && mdma- >error_status == 0)
+ mdma- >error_status = es;
+ spin_unlock(&mdma- >error_status_lock);
+
+ /* Handle interrupt on each channel */
+ mpc_dma_irq_process(mdma, in_be32(&mdma- >regs- >dmainth),
+ in_be32(&mdma- >regs- >dmaerrh), 32);
+ mpc_dma_irq_process(mdma, in_be32(&mdma- >regs- >dmaintl),
+ in_be32(&mdma- >regs- >dmaerrl), 0);
+
+ /* Ack interrupt on all channels */
+ out_be32(&mdma- >regs- >dmainth, 0xFFFFFFFF);
+ out_be32(&mdma- >regs- >dmaintl, 0xFFFFFFFF);
+ out_be32(&mdma- >regs- >dmaerrh, 0xFFFFFFFF);
+ out_be32(&mdma- >regs- >dmaerrl, 0xFFFFFFFF);
+
+ /* Schedule tasklet */
+ tasklet_schedule(&mdma- >tasklet);
+
+ return IRQ_HANDLED;
+}
+
+/* DMA Tasklet */
+static void mpc_dma_tasklet(unsigned long data)
+{
+ struct mpc_dma *mdma = (void *)data;
+ dma_cookie_t last_cookie = 0;
+ struct mpc_dma_chan *mchan;
+ struct mpc_dma_desc *mdesc;
+ struct dma_async_tx_descriptor *desc;
+ unsigned long flags;
+ LIST_HEAD(list);
+ uint es;
+ int i;
+
+ spin_lock_irqsave(&mdma- >error_status_lock, flags);
+ es = mdma- >error_status;
+ mdma- >error_status = 0;
+ spin_unlock_irqrestore(&mdma- >error_status_lock, flags);
+
+ /* Print nice error report */
+ if (es) {
+ dev_err(mdma- >dma.dev,
+ "Hardware reported following error(s) on channel %u:\n",
+ MPC_DMA_DMAES_ERRCHN(es));
+
+ if (es & MPC_DMA_DMAES_GPE)
+ dev_err(mdma- >dma.dev, "- Group Priority Error\n");
+ if (es & MPC_DMA_DMAES_CPE)
+ dev_err(mdma- >dma.dev, "- Channel Priority Error\n");
+ if (es & MPC_DMA_DMAES_SAE)
+ dev_err(mdma- >dma.dev, "- Source Address Error\n");
+ if (es & MPC_DMA_DMAES_SOE)
+ dev_err(mdma- >dma.dev, "- Source Offset"
+ " Configuration Error\n");
+ if (es & MPC_DMA_DMAES_DAE)
+ dev_err(mdma- >dma.dev, "- Destination Address"
+ " Error\n");
+ if (es & MPC_DMA_DMAES_DOE)
+ dev_err(mdma- >dma.dev, "- Destination Offset"
+ " Configuration Error\n");
+ if (es & MPC_DMA_DMAES_NCE)
+ dev_err(mdma- >dma.dev, "- NBytes/Citter"
+ " Configuration Error\n");
+ if (es & MPC_DMA_DMAES_SGE)
+ dev_err(mdma- >dma.dev, "- Scatter/Gather"
+ " Configuration Error\n");
+ if (es & MPC_DMA_DMAES_SBE)
+ dev_err(mdma- >dma.dev, "- Source Bus Error\n");
+ if (es & MPC_DMA_DMAES_DBE)
+ dev_err(mdma- >dma.dev, "- Destination Bus Error\n");
+ }
+
+ for (i = 0; i < mdma- >dma.chancnt; i++) {
+ mchan = &mdma- >channels[i];
+
+ /* Get all completed descriptors */
+ spin_lock_irqsave(&mchan- >lock, flags);
+ if (!list_empty(&mchan- >completed))
+ list_splice_tail_init(&mchan- >completed, &list);
+ spin_unlock_irqrestore(&mchan- >lock, flags);
+
+ if (list_empty(&list))
+ continue;
+
+ /* Execute callbacks and run dependencies */
+ list_for_each_entry(mdesc, &list, node) {
+ desc = &mdesc- >desc;
+
+ if (desc- >callback)
+ desc- >callback(desc- >callback_param);
+
+ last_cookie = desc- >cookie;
+ dma_run_dependencies(desc);
+ }
+
+ /* Free descriptors */
+ spin_lock_irqsave(&mchan- >lock, flags);
+ list_splice_tail_init(&list, &mchan- >free);
+ mchan- >completed_cookie = last_cookie;
+ spin_unlock_irqrestore(&mchan- >lock, flags);
+ }
+}
+
+/* Submit descriptor to hardware */
+static dma_cookie_t mpc_dma_tx_submit(struct dma_async_tx_descriptor *txd)
+{
+ struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(txd- >chan);
+ struct mpc_dma_desc *mdesc;
+ unsigned long flags;
+ dma_cookie_t cookie;
+
+ mdesc = container_of(txd, struct mpc_dma_desc, desc);
+
+ spin_lock_irqsave(&mchan- >lock, flags);
+
+ /* Move descriptor to queue */
+ list_move_tail(&mdesc- >node, &mchan- >queued);
+
+ /* If channel is idle, execute all queued descriptors */
+ if (list_empty(&mchan- >active))
+ mpc_dma_execute(mchan);
+
+ /* Update cookie */
+ cookie = mchan- >chan.cookie + 1;
+ if (cookie <= 0)
+ cookie = 1;
+
+ mchan- >chan.cookie = cookie;
+ mdesc- >desc.cookie = cookie;
+
+ spin_unlock_irqrestore(&mchan- >lock, flags);
+
+ return cookie;
+}
+
+/* Alloc channel resources */
+static int mpc_dma_alloc_chan_resources(struct dma_chan *chan)
+{
+ struct mpc_dma *mdma = dma_chan_to_mpc_dma(chan);
+ struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan);
+ struct mpc_dma_desc *mdesc;
+ struct mpc_dma_tcd *tcd;
+ dma_addr_t tcd_paddr;
+ unsigned long flags;
+ LIST_HEAD(descs);
+ int i;
+
+ /* Alloc DMA memory for Transfer Control Descriptors */
+ tcd = dma_alloc_coherent(mdma- >dma.dev,
+ MPC_DMA_DESCRIPTORS * sizeof(struct mpc_dma_tcd),
+ &tcd_paddr, GFP_KERNEL);
+ if (!tcd)
+ return -ENOMEM;
+
+ /* Alloc descriptors for this channel */
+ for (i = 0; i < MPC_DMA_DESCRIPTORS; i++) {
+ mdesc = kzalloc(sizeof(struct mpc_dma_desc), GFP_KERNEL);
+ if (!mdesc) {
+ dev_notice(mdma- >dma.dev, "Memory allocation error. "
+ "Allocated only %u descriptors\n", i);
+ break;
+ }
+
+ dma_async_tx_descriptor_init(&mdesc- >desc, chan);
+ mdesc- >desc.flags = DMA_CTRL_ACK;
+ mdesc- >desc.tx_submit = mpc_dma_tx_submit;
+
+ mdesc- >tcd = &tcd[i];
+ mdesc- >tcd_paddr = tcd_paddr + (i * sizeof(struct mpc_dma_tcd));
+
+ list_add_tail(&mdesc- >node, &descs);
+ }
+
+ /* Return error only if no descriptors were allocated */
+ if (i == 0) {
+ dma_free_coherent(mdma- >dma.dev,
+ MPC_DMA_DESCRIPTORS * sizeof(struct mpc_dma_tcd),
+ tcd, tcd_paddr);
+ return -ENOMEM;
+ }
+
+ spin_lock_irqsave(&mchan- >lock, flags);
+ mchan- >tcd = tcd;
+ mchan- >tcd_paddr = tcd_paddr;
+ list_splice_tail_init(&descs, &mchan- >free);
+ spin_unlock_irqrestore(&mchan- >lock, flags);
+
+ /* Enable Error Interrupt */
+ out_8(&mdma- >regs- >dmaseei, chan- >chan_id);
+
+ return 0;
+}
+
+/* Free channel resources */
+static void mpc_dma_free_chan_resources(struct dma_chan *chan)
+{
+ struct mpc_dma *mdma = dma_chan_to_mpc_dma(chan);
+ struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan);
+ struct mpc_dma_desc *mdesc, *tmp;
+ struct mpc_dma_tcd *tcd;
+ dma_addr_t tcd_paddr;
+ unsigned long flags;
+ LIST_HEAD(descs);
+
+ spin_lock_irqsave(&mchan- >lock, flags);
+
+ /* Channel must be idle */
+ BUG_ON(!list_empty(&mchan- >prepared));
+ BUG_ON(!list_empty(&mchan- >queued));
+ BUG_ON(!list_empty(&mchan- >active));
+ BUG_ON(!list_empty(&mchan- >completed));
+
+ /* Move data */
+ list_splice_tail_init(&mchan- >free, &descs);
+ tcd = mchan- >tcd;
+ tcd_paddr = mchan- >tcd_paddr;
+
+ spin_unlock_irqrestore(&mchan- >lock, flags);
+
+ /* Free DMA memory used by descriptors */
+ dma_free_coherent(mdma- >dma.dev,
+ MPC_DMA_DESCRIPTORS * sizeof(struct mpc_dma_tcd),
+ tcd, tcd_paddr);
+
+ /* Free descriptors */
+ list_for_each_entry_safe(mdesc, tmp, &descs, node)
+ kfree(mdesc);
+
+ /* Disable Error Interrupt */
+ out_8(&mdma- >regs- >dmaceei, chan- >chan_id);
+}
+
+/* Send all pending descriptor to hardware */
+static void mpc_dma_issue_pending(struct dma_chan *chan)
+{
+ /*
+ * We are posting descriptors to the hardware as soon as
+ * they are ready, so this function does nothing.
+ */
+}
+
+/* Check request completion status */
+static enum dma_status
+mpc_dma_is_tx_complete(struct dma_chan *chan, dma_cookie_t cookie,
+ dma_cookie_t *done, dma_cookie_t *used)
+{
+ struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan);
+ unsigned long flags;
+ dma_cookie_t last_used;
+ dma_cookie_t last_complete;
+
+ spin_lock_irqsave(&mchan- >lock, flags);
+ last_used = mchan- >chan.cookie;
+ last_complete = mchan- >completed_cookie;
+ spin_unlock_irqrestore(&mchan- >lock, flags);
+
+ if (done)
+ *done = last_complete;
+
+ if (used)
+ *used = last_used;
+
+ return dma_async_is_complete(cookie, last_complete, last_used);
+}
+
+/* Prepare descriptor for memory to memory copy */
+static struct dma_async_tx_descriptor *
+mpc_dma_prep_memcpy(struct dma_chan *chan, dma_addr_t dst, dma_addr_t src,
+ size_t len, unsigned long flags)
+{
+ struct mpc_dma_chan *mchan = dma_chan_to_mpc_dma_chan(chan);
+ struct mpc_dma_desc *mdesc = NULL;
+ struct mpc_dma_tcd *tcd;
+ unsigned long iflags;
+
+ /* Get free descriptor */
+ spin_lock_irqsave(&mchan- >lock, iflags);
+ if (!list_empty(&mchan- >free)) {
+ mdesc = list_first_entry(&mchan- >free, struct mpc_dma_desc,
+ node);
+ list_del(&mdesc- >node);
+ }
+ spin_unlock_irqrestore(&mchan- >lock, iflags);
+
+ if (!mdesc)
+ return NULL;
+
+ mdesc- >error = 0;
+ tcd = mdesc- >tcd;
+
+ /* Prepare Transfer Control Descriptor for this transaction */
+ memset(tcd, 0, sizeof(struct mpc_dma_tcd));
+
+ if (IS_ALIGNED(src | dst | len, 32)) {
+ tcd- >ssize = MPC_DMA_TSIZE_32;
+ tcd- >dsize = MPC_DMA_TSIZE_32;
+ tcd- >soff = 32;
+ tcd- >doff = 32;
+ } else if (IS_ALIGNED(src | dst | len, 16)) {
+ tcd- >ssize = MPC_DMA_TSIZE_16;
+ tcd- >dsize = MPC_DMA_TSIZE_16;
+ tcd- >soff = 16;
+ tcd- >doff = 16;
+ } else if (IS_ALIGNED(src | dst | len, 4)) {
+ tcd- >ssize = MPC_DMA_TSIZE_4;
+ tcd- >dsize = MPC_DMA_TSIZE_4;
+ tcd- >soff = 4;
+ tcd- >doff = 4;
+ } else if (IS_ALIGNED(src | dst | len, 2)) {
+ tcd- >ssize = MPC_DMA_TSIZE_2;
+ tcd- >dsize = MPC_DMA_TSIZE_2;
+ tcd- >soff = 2;
+ tcd- >doff = 2;
+ } else {
+ tcd- >ssize = MPC_DMA_TSIZE_1;
+ tcd- >dsize = MPC_DMA_TSIZE_1;
+ tcd- >soff = 1;
+ tcd- >doff = 1;
+ }
+
+ tcd- >saddr = src;
+ tcd- >daddr = dst;
+ tcd- >nbytes = len;
+ tcd- >biter = 1;
+ tcd- >citer = 1;
+
+ /* Place descriptor in prepared list */
+ spin_lock_irqsave(&mchan- >lock, iflags);
+ list_add_tail(&mdesc- >node, &mchan- >prepared);
+ spin_unlock_irqrestore(&mchan- >lock, iflags);
+
+ return &mdesc- >desc;
+}
+
+static int __init mpc_dma_probe(struct of_device *op,
+ const struct of_device_id *match)
+{
+ struct device_node *dn = op- >node;
+ struct device *dev = &op- >dev;
+ struct dma_device *dma;
+ struct mpc_dma *mdma;
+ struct mpc_dma_chan *mchan;
+ struct resource res;
+ ulong regs_start, regs_size;
+ int retval, i;
+
+ mdma = devm_kzalloc(dev, sizeof(struct mpc_dma), GFP_KERNEL);
+ if (!mdma) {
+ dev_err(dev, "Memory exhausted!\n");
+ return -ENOMEM;
+ }
+
+ mdma- >irq = irq_of_parse_and_map(dn, 0);
+ if (mdma- >irq == NO_IRQ) {
+ dev_err(dev, "Error mapping IRQ!\n");
+ return -EINVAL;
+ }
+
+ retval = of_address_to_resource(dn, 0, &res);
+ if (retval) {
+ dev_err(dev, "Error parsing memory region!\n");
+ return retval;
+ }
+
+ regs_start = res.start;
+ regs_size = res.end - res.start + 1;
+
+ if (!devm_request_mem_region(dev, regs_start, regs_size, DRV_NAME)) {
+ dev_err(dev, "Error requesting memory region!\n");
+ return -EBUSY;
+ }
+
+ mdma- >regs = devm_ioremap(dev, regs_start, regs_size);
+ if (!mdma- >regs) {
+ dev_err(dev, "Error mapping memory region!\n");
+ return -ENOMEM;
+ }
+
+ mdma- >tcd = (struct mpc_dma_tcd *)((u8 *)(mdma- >regs)
+ + MPC_DMA_TCD_OFFSET);
+
+ retval = devm_request_irq(dev, mdma- >irq, &mpc_dma_irq, 0, DRV_NAME,
+ mdma);
+ if (retval) {
+ dev_err(dev, "Error requesting IRQ!\n");
+ return -EINVAL;
+ }
+
+ spin_lock_init(&mdma- >error_status_lock);
+
+ dma = &mdma- >dma;
+ dma- >dev = dev;
+ dma- >chancnt = MPC_DMA_CHANNELS;
+ dma- >device_alloc_chan_resources = mpc_dma_alloc_chan_resources;
+ dma- >device_free_chan_resources = mpc_dma_free_chan_resources;
+ dma- >device_issue_pending = mpc_dma_issue_pending;
+ dma- >device_is_tx_complete = mpc_dma_is_tx_complete;
+ dma- >device_prep_dma_memcpy = mpc_dma_prep_memcpy;
+
+ INIT_LIST_HEAD(&dma- >channels);
+ dma_cap_set(DMA_MEMCPY, dma- >cap_mask);
+
+ for (i = 0; i < dma- >chancnt; i++) {
+ mchan = &mdma- >channels[i];
+
+ mchan- >chan.device = dma;
+ mchan- >chan.chan_id = i;
+ mchan- >chan.cookie = 1;
+ mchan- >completed_cookie = mchan- >chan.cookie;
+
+ INIT_LIST_HEAD(&mchan- >free);
+ INIT_LIST_HEAD(&mchan- >prepared);
+ INIT_LIST_HEAD(&mchan- >queued);
+ INIT_LIST_HEAD(&mchan- >active);
+ INIT_LIST_HEAD(&mchan- >completed);
+
+ spin_lock_init(&mchan- >lock);
+ list_add_tail(&mchan- >chan.device_node, &dma- >channels);
+ }
+
+ tasklet_init(&mdma- >tasklet, mpc_dma_tasklet, (unsigned long)mdma);
+
+ /*
+ * Configure DMA Engine:
+ * - Dynamic clock,
+ * - Round-robin group arbitration,
+ * - Round-robin channel arbitration.
+ */
+ out_be32(&mdma- >regs- >dmacr, MPC_DMA_DMACR_EDCG |
+ MPC_DMA_DMACR_ERGA | MPC_DMA_DMACR_ERCA);
+
+ /* Disable hardware DMA requests */
+ out_be32(&mdma- >regs- >dmaerqh, 0);
+ out_be32(&mdma- >regs- >dmaerql, 0);
+
+ /* Disable error interrupts */
+ out_be32(&mdma- >regs- >dmaeeih, 0);
+ out_be32(&mdma- >regs- >dmaeeil, 0);
+
+ /* Clear interrupts status */
+ out_be32(&mdma- >regs- >dmainth, 0xFFFFFFFF);
+ out_be32(&mdma- >regs- >dmaintl, 0xFFFFFFFF);
+ out_be32(&mdma- >regs- >dmaerrh, 0xFFFFFFFF);
+ out_be32(&mdma- >regs- >dmaerrl, 0xFFFFFFFF);
+
+ /* Route interrupts to IPIC */
+ out_be32(&mdma- >regs- >dmaihsa, 0);
+ out_be32(&mdma- >regs- >dmailsa, 0);
+
+ /* Register DMA engine */
+ dev_set_drvdata(dev, mdma);
+ retval = dma_async_device_register(dma);
+ if (retval)
+ devm_free_irq(dev, mdma- >irq, mdma);
+
+ return retval;
+}
+
+static void __exit mpc_dma_remove(struct of_device *op)
+{
+ struct device *dev = &op- >dev;
+ struct mpc_dma *mdma = dev_get_drvdata(dev);
+
+ devm_free_irq(dev, mdma- >irq, mdma);
+}
+
+static struct of_device_id mpc_dma_match[] = {
+ { .compatible = "fsl,mpc512x-dma", },
+ {},
+};
+
+static struct of_platform_driver mpc_dma_driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ .match_table = mpc_dma_match,
+ .probe = mpc_dma_probe,
+ .remove = __exit_p(mpc_dma_remove),
+ .suspend = NULL,
+ .resume = NULL,
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init mpc_dma_init(void)
+{
+ return of_register_platform_driver(&mpc_dma_driver);
+}
+
+static void __exit mpc_dma_exit(void)
+{
+ of_unregister_platform_driver(&mpc_dma_driver);
+}
+
+module_init(mpc_dma_init);
+module_exit(mpc_dma_exit);
+
+/* MODULE API */
+MODULE_VERSION("0.5");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Piotr Ziecik <kosmo@semihalf.com >");
diff --git a/drivers/dma/mpc512x_dma.h b/drivers/dma/mpc512x_dma.h
new file mode 100644
index 0000000..867415b
--- /dev/null
+++ b/drivers/dma/mpc512x_dma.h
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) Freescale Semicondutor, Inc. 2007, 2008.
+ * Copyright (C) Semihalf, 2009.
+ *
+ * Written by Piotr Ziecik <kosmo@semihalf.com >. Hardware description
+ * (defines, structures and comments) was taken from MPC5121 DMA driver
+ * written by Hongjun Chen <hong-jun.chen@freescale.com >.
+ *
+ * Approved as OSADL project by a majority of OSADL members and funded
+ * by OSADL membership fees in 2009; for details see www.osadl.org.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * The full GNU General Public License is included in this distribution in the
+ * file called COPYING.
+ */
+#ifndef _MPC512X_DMA_H_
+#define _MPC512X_DMA_H_
+
+#include <linux/dmaengine.h >
+
+/* Number of DMA Transfer descriptors allocated per channel */
+#define MPC_DMA_DESCRIPTORS 64
+
+/* Macro definitions */
+#define MPC_DMA_CHANNELS 64
+#define MPC_DMA_TCD_OFFSET 0x1000
+
+/* Arbitration mode of group and channel */
+#define MPC_DMA_DMACR_EDCG (1 < < 31)
+#define MPC_DMA_DMACR_ERGA (1 < < 3)
+#define MPC_DMA_DMACR_ERCA (1 < < 2)
+
+/* Error codes */
+#define MPC_DMA_DMAES_VLD (1 < < 31)
+#define MPC_DMA_DMAES_GPE (1 < < 15)
+#define MPC_DMA_DMAES_CPE (1 < < 14)
+#define MPC_DMA_DMAES_ERRCHN(err) \
+ (((err) > > 8) & 0x3f)
+#define MPC_DMA_DMAES_SAE (1 < < 7)
+#define MPC_DMA_DMAES_SOE (1 < < 6)
+#define MPC_DMA_DMAES_DAE (1 < < 5)
+#define MPC_DMA_DMAES_DOE (1 < < 4)
+#define MPC_DMA_DMAES_NCE (1 < < 3)
+#define MPC_DMA_DMAES_SGE (1 < < 2)
+#define MPC_DMA_DMAES_SBE (1 < < 1)
+#define MPC_DMA_DMAES_DBE (1 < < 0)
+
+#define MPC_DMA_TSIZE_1 0x00
+#define MPC_DMA_TSIZE_2 0x01
+#define MPC_DMA_TSIZE_4 0x02
+#define MPC_DMA_TSIZE_16 0x04
+#define MPC_DMA_TSIZE_32 0x05
+
+/* MPC5121 DMA engine registers */
+struct __attribute__ ((__packed__)) mpc_dma_regs {
+ /* 0x00 */
+ u32 dmacr; /* DMA control register */
+ u32 dmaes; /* DMA error status */
+ /* 0x08 */
+ u32 dmaerqh; /* DMA enable request high(channels 63~32) */
+ u32 dmaerql; /* DMA enable request low(channels 31~0) */
+ u32 dmaeeih; /* DMA enable error interrupt high(ch63~32) */
+ u32 dmaeeil; /* DMA enable error interrupt low(ch31~0) */
+ /* 0x18 */
+ u8 dmaserq; /* DMA set enable request */
+ u8 dmacerq; /* DMA clear enable request */
+ u8 dmaseei; /* DMA set enable error interrupt */
+ u8 dmaceei; /* DMA clear enable error interrupt */
+ /* 0x1c */
+ u8 dmacint; /* DMA clear interrupt request */
+ u8 dmacerr; /* DMA clear error */
+ u8 dmassrt; /* DMA set start bit */
+ u8 dmacdne; /* DMA clear DONE status bit */
+ /* 0x20 */
+ u32 dmainth; /* DMA interrupt request high(ch63~32) */
+ u32 dmaintl; /* DMA interrupt request low(ch31~0) */
+ u32 dmaerrh; /* DMA error high(ch63~32) */
+ u32 dmaerrl; /* DMA error low(ch31~0) */
+ /* 0x30 */
+ u32 dmahrsh; /* DMA hw request status high(ch63~32) */
+ u32 dmahrsl; /* DMA hardware request status low(ch31~0) */
+ u32 dmaihsa; /* DMA interrupt high select AXE(ch63~32) */
+ u32 dmailsa; /* DMA interrupt low select AXE(ch31~0) */
+ /* 0x40 ~ 0xff */
+ u32 reserve0[48]; /* Reserved */
+ /* 0x100 */
+ u8 dchpri[MPC_DMA_CHANNELS];
+ /* DMA channels(0~63) priority */
+};
+
+struct __attribute__ ((__packed__)) mpc_dma_tcd {
+ /* 0x00 */
+ u32 saddr; /* Source address */
+
+ u32 smod:5; /* Source address modulo */
+ u32 ssize:3; /* Source data transfer size */
+ u32 dmod:5; /* Destination address modulo */
+ u32 dsize:3; /* Destination data transfer size */
+ u32 soff:16; /* Signed source address offset */
+
+ /* 0x08 */
+ u32 nbytes; /* Inner "minor" byte count */
+ u32 slast; /* Last source address adjustment */
+ u32 daddr; /* Destination address */
+
+ /* 0x14 */
+ u32 citer_elink:1; /* Enable channel-to-channel linking on
+ * minor loop complete
+ */
+ u32 citer_linkch:6; /* Link channel for minor loop complete */
+ u32 citer:9; /* Current "major" iteration count */
+ u32 doff:16; /* Signed destination address offset */
+
+ /* 0x18 */
+ u32 dlast_sga; /* Last Destination address adjustment/scatter
+ * gather address
+ */
+
+ /* 0x1c */
+ u32 biter_elink:1; /* Enable channel-to-channel linking on major
+ * loop complete
+ */
+ u32 biter_linkch:6;
+ u32 biter:9; /* Beginning "major" iteration count */
+ u32 bwc:2; /* Bandwidth control */
+ u32 major_linkch:6; /* Link channel number */
+ u32 done:1; /* Channel done */
+ u32 active:1; /* Channel active */
+ u32 major_elink:1; /* Enable channel-to-channel linking on major
+ * loop complete
+ */
+ u32 e_sg:1; /* Enable scatter/gather processing */
+ u32 d_req:1; /* Disable request */
+ u32 int_half:1; /* Enable an interrupt when major counter is
+ * half complete
+ */
+ u32 int_maj:1; /* Enable an interrupt when major iteration
+ * count completes
+ */
+ u32 start:1; /* Channel start */
+};
+
+struct mpc_dma_desc {
+ struct dma_async_tx_descriptor desc;
+ struct mpc_dma_tcd *tcd;
+ dma_addr_t tcd_paddr;
+ int error;
+ struct list_head node;
+};
+
+struct mpc_dma_chan {
+ struct dma_chan chan;
+ struct list_head free;
+ struct list_head prepared;
+ struct list_head queued;
+ struct list_head active;
+ struct list_head completed;
+ struct mpc_dma_tcd *tcd;
+ dma_addr_t tcd_paddr;
+ dma_cookie_t completed_cookie;
+
+ /* Lock for this structure */
+ spinlock_t lock;
+};
+
+struct mpc_dma {
+ struct dma_device dma;
+ struct tasklet_struct tasklet;
+ struct mpc_dma_chan channels[MPC_DMA_CHANNELS];
+ struct mpc_dma_regs __iomem *regs;
+ struct mpc_dma_tcd __iomem *tcd;
+ int irq;
+ uint error_status;
+
+ /* Lock for error_status field in this structure */
+ spinlock_t error_status_lock;
+};
+
+#endif /* _MPC512X_DMA_H_ */
--
1.6.0.6
------------------------------
_______________________________________________
Linuxppc-dev mailing list
Linuxppc-dev@ozlabs.org
https://ozlabs.org/mailman/listinfo/linuxppc-dev
End of Linuxppc-dev Digest, Vol 57, Issue 42
********************************************
^ permalink raw reply related
* [PATCH v2] drivers/hvc: add missing __devexit_p()
From: Mike Frysinger @ 2009-05-18 7:44 UTC (permalink / raw)
To: linux-kernel; +Cc: linuxppc-dev
In-Reply-To: <1242632521-15954-1-git-send-email-vapier@gentoo.org>
The remove function uses __devexit, so the .remove assignment needs
__devexit_p() to fix a build error with hotplug disabled.
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
CC: linuxppc-dev@ozlabs.org
---
v2
- include all hvc files
drivers/char/hvc_iseries.c | 2 +-
drivers/char/hvc_vio.c | 2 +-
drivers/char/hvcs.c | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/char/hvc_iseries.c b/drivers/char/hvc_iseries.c
index 449727b..936d05b 100644
--- a/drivers/char/hvc_iseries.c
+++ b/drivers/char/hvc_iseries.c
@@ -241,7 +241,7 @@ static int __devexit hvc_vio_remove(struct vio_dev *vdev)
static struct vio_driver hvc_vio_driver = {
.id_table = hvc_driver_table,
.probe = hvc_vio_probe,
- .remove = hvc_vio_remove,
+ .remove = __devexit_p(hvc_vio_remove),
.driver = {
.name = hvc_driver_name,
.owner = THIS_MODULE,
diff --git a/drivers/char/hvc_vio.c b/drivers/char/hvc_vio.c
index bd62dc8..c72b994 100644
--- a/drivers/char/hvc_vio.c
+++ b/drivers/char/hvc_vio.c
@@ -113,7 +113,7 @@ static int __devexit hvc_vio_remove(struct vio_dev *vdev)
static struct vio_driver hvc_vio_driver = {
.id_table = hvc_driver_table,
.probe = hvc_vio_probe,
- .remove = hvc_vio_remove,
+ .remove = __devexit_p(hvc_vio_remove),
.driver = {
.name = hvc_driver_name,
.owner = THIS_MODULE,
diff --git a/drivers/char/hvcs.c b/drivers/char/hvcs.c
index c76bccf..2724d62 100644
--- a/drivers/char/hvcs.c
+++ b/drivers/char/hvcs.c
@@ -868,7 +868,7 @@ static int __devexit hvcs_remove(struct vio_dev *dev)
static struct vio_driver hvcs_vio_driver = {
.id_table = hvcs_driver_table,
.probe = hvcs_probe,
- .remove = hvcs_remove,
+ .remove = __devexit_p(hvcs_remove),
.driver = {
.name = hvcs_driver_name,
.owner = THIS_MODULE,
--
1.6.3
^ permalink raw reply related
* [PATCH] drivers/hvc: add missing __devexit_p()
From: Mike Frysinger @ 2009-05-18 7:42 UTC (permalink / raw)
To: linux-kernel; +Cc: linuxppc-dev
The remove function uses __devexit, so the .remove assignment needs
__devexit_p() to fix a build error with hotplug disabled.
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
CC: linuxppc-dev@ozlabs.org
CC: linux-kernel@vger.kernel.org
---
drivers/char/hvc_iseries.c | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/drivers/char/hvc_iseries.c b/drivers/char/hvc_iseries.c
index 449727b..936d05b 100644
--- a/drivers/char/hvc_iseries.c
+++ b/drivers/char/hvc_iseries.c
@@ -241,7 +241,7 @@ static int __devexit hvc_vio_remove(struct vio_dev *vdev)
static struct vio_driver hvc_vio_driver = {
.id_table = hvc_driver_table,
.probe = hvc_vio_probe,
- .remove = hvc_vio_remove,
+ .remove = __devexit_p(hvc_vio_remove),
.driver = {
.name = hvc_driver_name,
.owner = THIS_MODULE,
--
1.6.3
^ permalink raw reply related
* "test" branch updates
From: Benjamin Herrenschmidt @ 2009-05-18 7:19 UTC (permalink / raw)
To: linuxppc-dev list
Just a quick note before I leave the office...
I've just pushed out a bunch of stuff to my "test" branch. There's a
couple of known breakage (iseries and 8xx) which will have to be sorted
out before that stuff goes into "next". hopefully in a couple of days.
Still... feel free to take a peek, test, etc...
Cheers,
Ben.
^ permalink raw reply
* Re: [PATCH v2] powerpc/pci: clean up direct access to sysdata by iseries platform
From: Benjamin Herrenschmidt @ 2009-05-18 7:05 UTC (permalink / raw)
To: Kumar Gala; +Cc: Stephen Rothwell, Linuxppc-dev Development
In-Reply-To: <8530F7E1-6EA9-4DA3-9130-DDA1C6E4B510@kernel.crashing.org>
On Fri, 2009-05-15 at 07:50 -0500, Kumar Gala wrote:
> On May 15, 2009, at 7:47 AM, Kumar Gala wrote:
>
> > We shouldn't directly access sysdata to get the device node. We
> > should
> > be calling pci_device_to_OF_node().
> >
> > Signed-off-by: Kumar Gala <galak@kernel.crashing.org>
> > ---
> > * Updated based on sfr's iseries pci fix patch
> >
> > arch/powerpc/platforms/iseries/iommu.c | 2 +-
> > arch/powerpc/platforms/iseries/pci.c | 8 ++++----
> > 2 files changed, 5 insertions(+), 5 deletions(-)
>
> Stephen if you can test this version that would be great.
Fails with:
/home/benh/linux-powerpc-test/arch/powerpc/platforms/iseries/iommu.c: In function ‘pci_dma_dev_setup_iseries’:
/home/benh/linux-powerpc-test/arch/powerpc/platforms/iseries/iommu.c:180: error: ‘dev’ undeclared (first use in this function)
/home/benh/linux-powerpc-test/arch/powerpc/platforms/iseries/iommu.c:180: error: (Each undeclared identifier is reported only once
/home/benh/linux-powerpc-test/arch/powerpc/platforms/iseries/iommu.c:180: error: for each function it appears in.)
(I suspect it should have been "pdev" instead of "dev")
Cheers,
Ben.
^ permalink raw reply
* Re: [PATCH] powerpc: Make the NR_CPUS max 8192
From: Michael Neuling @ 2009-05-18 7:01 UTC (permalink / raw)
To: Geert Uytterhoeven; +Cc: linuxppc-dev, paulus
In-Reply-To: <alpine.LRH.2.00.0905180857060.16754@vixen.sonytel.be>
In message <alpine.LRH.2.00.0905180857060.16754@vixen.sonytel.be> you wrote:
> On Mon, 18 May 2009, Michael Neuling wrote:
> > > > We can compile and boot with NR_CPUS=3D8192, so make this the max. 102
4
> > > > was an arbitrary decision anyway.
> > >
> > > Is 8192 still arbitrary? Or does something break above that?
> >
> > Yeah, the compile breaks after that with 4K pages.
> >
> > In drivers/base/node.c we have:
> > /* 2008/04/07: buf currently PAGE_SIZE, need 9 chars per 32 bits. */
> > BUILD_BUG_ON((NR_CPUS/32 * 9) > (PAGE_SIZE-1));
> > which causes:
> > drivers/base/node.c: In function 'node_read_cpumap':
> > drivers/base/node.c:31: error: size of array 'type name' is negative
> >
> > I can compile with 16384 CPUs with 64K pages, but it doesn't boot.
> >
> > sfr asked for size info for different builds, so I may as well repost
> > them here:
> >
> > text data bss dec hex filenam
e
> > 9237767 3225768 4409996 16873531 101783b vmlinux
.1024
> > 9247355 4769472 7373708 21390535 14664c7 vmlinux
.2048
> > 9267239 7857032 13301132 30425403 1d0413b vmlinux
.4096
> > 9302623 14035832 25155980 48494435 2e3f763 vmlinux
.8192
> > 9373283 26389360 48865676 84628319 50b535f vmlinux
.16384
>
> Will distros now start pushing NR_CPUS=8192-kernels on us?
Yeah, that's a concern.
Mikey
^ permalink raw reply
* fs_enet build breakage
From: Benjamin Herrenschmidt @ 2009-05-18 7:01 UTC (permalink / raw)
To: Kumar Gala; +Cc: linuxppc-dev list
Hi Kumar
Commit 4484079d517c2b6521621be0b1ea246ccc55c7d7 from your next branch
breaks my 8xx test config with the following error:
/home/benh/linux-powerpc-test/drivers/net/fs_enet/fs_enet-main.c: In function ‘setup_immap’:
/home/benh/linux-powerpc-test/drivers/net/fs_enet/fs_enet-main.c:947: error: ‘IMAP_ADDR’ undeclared (first use in this function)
/home/benh/linux-powerpc-test/drivers/net/fs_enet/fs_enet-main.c:947: error: (Each undeclared identifier is reported only once
/home/benh/linux-powerpc-test/drivers/net/fs_enet/fs_enet-main.c:947: error: for each function it appears in.)
I'm still pushing it with a bunch of other stuff to "test" so people get
to find more breakage but it will need to be fixed for the actual "next"
branch.
Cheers,
Ben.
^ permalink raw reply
* Re: [PATCH] powerpc: Make the NR_CPUS max 8192
From: Geert Uytterhoeven @ 2009-05-18 6:57 UTC (permalink / raw)
To: Michael Neuling; +Cc: linuxppc-dev, paulus
In-Reply-To: <9686.1242620044@neuling.org>
On Mon, 18 May 2009, Michael Neuling wrote:
> > > We can compile and boot with NR_CPUS=3D8192, so make this the max. 1024
> > > was an arbitrary decision anyway.
> >
> > Is 8192 still arbitrary? Or does something break above that?
>
> Yeah, the compile breaks after that with 4K pages.
>
> In drivers/base/node.c we have:
> /* 2008/04/07: buf currently PAGE_SIZE, need 9 chars per 32 bits. */
> BUILD_BUG_ON((NR_CPUS/32 * 9) > (PAGE_SIZE-1));
> which causes:
> drivers/base/node.c: In function 'node_read_cpumap':
> drivers/base/node.c:31: error: size of array 'type name' is negative
>
> I can compile with 16384 CPUs with 64K pages, but it doesn't boot.
>
> sfr asked for size info for different builds, so I may as well repost
> them here:
>
> text data bss dec hex filename
> 9237767 3225768 4409996 16873531 101783b vmlinux.1024
> 9247355 4769472 7373708 21390535 14664c7 vmlinux.2048
> 9267239 7857032 13301132 30425403 1d0413b vmlinux.4096
> 9302623 14035832 25155980 48494435 2e3f763 vmlinux.8192
> 9373283 26389360 48865676 84628319 50b535f vmlinux.16384
Will distros now start pushing NR_CPUS=8192-kernels on us?
With kind regards,
Geert Uytterhoeven
Software Architect
Techsoft Centre
Technology and Software Centre Europe
The Corporate Village · Da Vincilaan 7-D1 · B-1935 Zaventem · Belgium
Phone: +32 (0)2 700 8453
Fax: +32 (0)2 700 8622
E-mail: Geert.Uytterhoeven@sonycom.com
Internet: http://www.sony-europe.com/
A division of Sony Europe (Belgium) N.V.
VAT BE 0413.825.160 · RPR Brussels
Fortis · BIC GEBABEBB · IBAN BE41293037680010
^ permalink raw reply
* [git pull] Please pull powerpc.git merge branch
From: Benjamin Herrenschmidt @ 2009-05-18 6:34 UTC (permalink / raw)
To: Linus Torvalds; +Cc: linuxppc-dev list, Andrew Morton, Linux Kernel list
Hi Linus !
A few more 2.6.30 things for you... a defconfig update, and a couple
of small fixes, either obvious enough or regression fixes.
Cheers,
Ben.
The following changes since commit 86460103c412f9e11aeb7950cce64b9e51539d4d:
Linus Torvalds (1):
Merge branch 'for-linus' of git://git.kernel.org/.../rafael/suspend-2.6
are available in the git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git merge
Benjamin Herrenschmidt (1):
powerpc: Explicit alignment for .data.cacheline_aligned
Geoff Levand (1):
powerpc/ps3: Update ps3_defconfig
Mel Gorman (1):
powerpc: Do not assert pte_locked for hugepage PTE entries
Michael Ellerman (1):
powerpc/ftrace: Use pr_devel() in ftrace.c
Steven Rostedt (1):
powerpc/ftrace: Fix constraint to be early clobber
arch/powerpc/configs/ps3_defconfig | 105 +++++++++++++++++++++---------------
arch/powerpc/kernel/ftrace.c | 22 ++++----
arch/powerpc/kernel/vmlinux.lds.S | 1 +
arch/powerpc/mm/pgtable.c | 3 +-
4 files changed, 76 insertions(+), 55 deletions(-)
^ permalink raw reply
* Re: [PATCH] bug fix in arch/powerpc/mm/tlb_nohash_low.S
From: Benjamin Herrenschmidt @ 2009-05-18 5:52 UTC (permalink / raw)
To: Stephen Rothwell; +Cc: linuxppc-dev, Torez Smith
In-Reply-To: <20090518154721.0a9c3dee.sfr@canb.auug.org.au>
On Mon, 2009-05-18 at 15:47 +1000, Stephen Rothwell wrote:
> Hi Ben,
>
> On Mon, 18 May 2009 15:12:03 +1000 Benjamin Herrenschmidt <benh@kernel.crashing.org> wrote:
> >
> > Thanks, but it's been fixed already :-)
> >
> > See commit b62c31ae401c6df25c61b206681a6e904ef97169.
>
> Should this be submitted for 2.6.29 stable?
Probably, will do.
Cheers,
Ben.
^ permalink raw reply
* Re: [PATCH] bug fix in arch/powerpc/mm/tlb_nohash_low.S
From: Stephen Rothwell @ 2009-05-18 5:47 UTC (permalink / raw)
To: Benjamin Herrenschmidt; +Cc: linuxppc-dev, Torez Smith
In-Reply-To: <1242623523.18075.39.camel@pasglop>
[-- Attachment #1: Type: text/plain, Size: 366 bytes --]
Hi Ben,
On Mon, 18 May 2009 15:12:03 +1000 Benjamin Herrenschmidt <benh@kernel.crashing.org> wrote:
>
> Thanks, but it's been fixed already :-)
>
> See commit b62c31ae401c6df25c61b206681a6e904ef97169.
Should this be submitted for 2.6.29 stable?
--
Cheers,
Stephen Rothwell sfr@canb.auug.org.au
http://www.canb.auug.org.au/~sfr/
[-- Attachment #2: Type: application/pgp-signature, Size: 197 bytes --]
^ permalink raw reply
* [PATCH] powerpc: Explicit alignment for .data.cacheline_aligned
From: Benjamin Herrenschmidt @ 2009-05-18 5:17 UTC (permalink / raw)
To: linuxppc-dev
I don't think anything guarantees that the objects in data.page_aligned
are a multiple of PAGE_SIZE, thus the section may end on any boundary.
So the following section, .data.cacheline_aligned needs an explicit
alignment.
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
---
And this is the right patch after a "quilt ref" and without typo :-)
arch/powerpc/kernel/vmlinux.lds.S | 1 +
1 file changed, 1 insertion(+)
--- linux-work.orig/arch/powerpc/kernel/vmlinux.lds.S 2009-05-18 14:24:05.000000000 +1000
+++ linux-work/arch/powerpc/kernel/vmlinux.lds.S 2009-05-18 15:16:54.000000000 +1000
@@ -264,6 +264,7 @@ SECTIONS
*(.data.page_aligned)
}
+ . = ALIGN(L1_CACHE_BYTES);
.data.cacheline_aligned : AT(ADDR(.data.cacheline_aligned) - LOAD_OFFSET) {
*(.data.cacheline_aligned)
}
^ permalink raw reply
* Re: [PATCH] bug fix in arch/powerpc/mm/tlb_nohash_low.S
From: Benjamin Herrenschmidt @ 2009-05-18 5:12 UTC (permalink / raw)
To: Torez Smith; +Cc: linuxppc-dev
In-Reply-To: <1242622146.31702.26.camel@torez.austin.ibm.com>
On Sun, 2009-05-17 at 23:49 -0500, Torez Smith wrote:
> File arch/powerpc/mm/tlb_nohash_low.S defines various processor specific low level TLB invalidation. Most all family of validations are grouped via pre-processor defines with the intent we error out if we reach the end and our platform is not represented. Given this, the last few lines of the file should look similar to the following....
> << SNIP >>
> 1: wrtee r10
> blr
> #else
> #error Unsupported processor type !
> #endif
>
> However, the #else is incorrectly written as #elif. On some of the newer compilers/assemblers, this will not successfully assemble and will cause an error.
Thanks, but it's been fixed already :-)
See commit b62c31ae401c6df25c61b206681a6e904ef97169.
Cheers,
Ben.
^ permalink raw reply
* [PATCH] bug fix in arch/powerpc/mm/tlb_nohash_low.S
From: Torez Smith @ 2009-05-18 4:49 UTC (permalink / raw)
To: Benjamin Herrenschmidt, linuxppc-dev
File arch/powerpc/mm/tlb_nohash_low.S defines various processor specific low level TLB invalidation. Most all family of validations are grouped via pre-processor defines with the intent we error out if we reach the end and our platform is not represented. Given this, the last few lines of the file should look similar to the following....
<< SNIP >>
1: wrtee r10
blr
#else
#error Unsupported processor type !
#endif
However, the #else is incorrectly written as #elif. On some of the newer compilers/assemblers, this will not successfully assemble and will cause an error.
Signed-off-by: Torez Smith <lnxtorez@linux.vnet.ibm.com>
---
Index: linux-2.6.29/arch/powerpc/mm/tlb_nohash_low.S
===================================================================
--- linux-2.6.29.orig/arch/powerpc/mm/tlb_nohash_low.S 2009-05-17 21:31:20.000000000 -0500
+++ linux-2.6.29/arch/powerpc/mm/tlb_nohash_low.S 2009-05-17 21:31:40.000000000 -0500
@@ -161,6 +161,6 @@
isync
1: wrtee r10
blr
-#elif
+#else
#error Unsupported processor type !
#endif
--
Torez Smith
IBM Linux Technology Center
^ permalink raw reply
* Re: [PATCH V2 2/3] powerpc: Add support for swiotlb on 32-bit
From: Benjamin Herrenschmidt @ 2009-05-18 4:49 UTC (permalink / raw)
To: Becky Bruce; +Cc: fujita.tomonori, linuxppc-dev
In-Reply-To: <1242340949-16369-2-git-send-email-beckyb@kernel.crashing.org>
On Thu, 2009-05-14 at 17:42 -0500, Becky Bruce wrote:
> This patch includes the basic infrastructure to use swiotlb
> bounce buffering on 32-bit powerpc. It is not yet enabled on
> any platforms. Probably the most interesting bit is the
> addition of addr_needs_map to dma_ops - we need this as
> a dma_op because the decision of whether or not an addr
> can be mapped by a device is device-specific.
>
> Signed-off-by: Becky Bruce <beckyb@kernel.crashing.org>
Hi Becky !
Finally I got to look at your patch :-)
A few comments below...
> #ifdef CONFIG_NOT_COHERENT_CACHE
> /*
> * DMA-consistent mapping functions for PowerPCs that don't support
> @@ -76,6 +85,8 @@ struct dma_mapping_ops {
> dma_addr_t dma_address, size_t size,
> enum dma_data_direction direction,
> struct dma_attrs *attrs);
> + int (*addr_needs_map)(struct device *dev, dma_addr_t addr,
> + size_t size);
What annoys me here is that we basically end up with two indirect
function calls for pretty much any DMA map. One was bad enough on low
end processors or very intensive networking, but this is getting real
bad don't you think ?
Granted, this is only used when swiotlb is used too, but still...
So the problem is that the region that can pass-through is somewhat
a mix of bus specific (incoming DMA window location & size) and
device specific (device addressing limitations).
Now, if we can always reduce it to a single range though, which I
think is practically the case, can't we instead pre-calculate that
range and stick -that- in the struct dev archdata or similar thus
speeding up the decision for a given address as to whether it needs
a swiotlb mapping or not ? Or does it gets too messy ?
Cheers,
Ben.
^ permalink raw reply
* [PATCH] powerpc: Explicit alignment for .data.cacheline_aligned
From: Benjamin Herrenschmidt @ 2009-05-18 4:29 UTC (permalink / raw)
To: linuxppc-dev
I don't think anything guarantees that the objects in data.page_aligned
are a multiple of PAGE_SIZE, thus the section may end on any boundary.
So the following section, .data.cacheline_aligned needs an explicit
alignment.
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
---
arch/powerpc/kernel/vmlinux.lds.S | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
--- linux-work.orig/arch/powerpc/kernel/vmlinux.lds.S 2009-05-18 14:24:05.000000000 +1000
+++ linux-work/arch/powerpc/kernel/vmlinux.lds.S 2009-05-18 14:24:22.000000000 +1000
@@ -264,7 +264,8 @@ SECTIONS
*(.data.page_aligned)
}
- .data.cacheline_aligned : AT(ADDR(.data.cacheline_aligned) - LOAD_OFFSET) {
+ . = ALIGN(L1_CACHE_BYTES);
+ .cacheline_aligned : AT(ADDR(.data.cacheline_aligned) - LOAD_OFFSET) {
*(.data.cacheline_aligned)
}
^ permalink raw reply
* Re: [PATCH 5/8] powerpc: use new macro for .data.cacheline_aligned section.
From: Benjamin Herrenschmidt @ 2009-05-18 4:23 UTC (permalink / raw)
To: Tim Abbott
Cc: Denys Vlasenko, Jeff Arnold, Linux kernel mailing list,
Anders Kaseorg, linuxppc-dev, Paul Mackerras, Sam Ravnborg,
Waseem Daher
In-Reply-To: <1241135777-9462-6-git-send-email-tabbott@mit.edu>
On Thu, 2009-04-30 at 19:56 -0400, Tim Abbott wrote:
> .data.cacheline_aligned should not need a separate output section;
> this change moves it into the .data section.
>
> Since there isn't an ALIGN() directive before the
> .data.cacheline_aligned scetion in the current linker script, I'd like
> an ack from one of the powerpc maintainers that L1_CACHE_BYTES is the
> correct alignment here.
Yes, that's the right size.
Also. that looks like a bug to me in the current stuff. I'm queuing your
patch set for .31 but I'll stick a "fix" in .30 for that just in case
which unfortunately means your patches will probably need to be rebased.
Cheers,
Ben.
> Signed-off-by: Tim Abbott <tabbott@mit.edu>
> Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
> Cc: Paul Mackerras <paulus@samba.org>
> Cc: linuxppc-dev@ozlabs.org
> ---
> arch/powerpc/kernel/vmlinux.lds.S | 5 +----
> 1 files changed, 1 insertions(+), 4 deletions(-)
>
> diff --git a/arch/powerpc/kernel/vmlinux.lds.S b/arch/powerpc/kernel/vmlinux.lds.S
> index 4223892..e769717 100644
> --- a/arch/powerpc/kernel/vmlinux.lds.S
> +++ b/arch/powerpc/kernel/vmlinux.lds.S
> @@ -223,6 +223,7 @@ SECTIONS
> .data : AT(ADDR(.data) - LOAD_OFFSET) {
> PAGE_ALIGNED_DATA
> NOSAVE_DATA
> + CACHELINE_ALIGNED_DATA(L1_CACHE_BYTES)
> DATA_DATA
> #ifdef CONFIG_PPC32
> *(.sdata)
> @@ -260,10 +261,6 @@ SECTIONS
> *(.data.init_task)
> }
>
> - .data.cacheline_aligned : AT(ADDR(.data.cacheline_aligned) - LOAD_OFFSET) {
> - *(.data.cacheline_aligned)
> - }
> -
> . = ALIGN(L1_CACHE_BYTES);
> .data.read_mostly : AT(ADDR(.data.read_mostly) - LOAD_OFFSET) {
> *(.data.read_mostly)
^ permalink raw reply
* Re: [PATCH] mv643xx_eth: fix PPC DMA breakage
From: David Miller @ 2009-05-18 4:16 UTC (permalink / raw)
To: buytenh; +Cc: netdev, linuxppc-dev, pacman, tbm
In-Reply-To: <20090515235330.GC1727@mail.wantstofly.org>
From: Lennert Buytenhek <buytenh@wantstofly.org>
Date: Sat, 16 May 2009 01:53:30 +0200
> On Fri, May 15, 2009 at 08:39:24PM +0200, Gabriel Paubert wrote:
>
>> After 2.6.29, PPC no more admits passing NULL to the dev parameter of
>> the DMA API. The result is a BUG followed by solid lock-up when the
>> mv643xx_eth driver brings an interface up. The following patch makes
>> the driver work on my Pegasos again; it is mostly a search and replace
>> of NULL by mp->dev->dev.parent in dma allocation/freeing/mapping/unmapping
>> functions.
>>
>> Signed-off-by: Gabriel Paubert <paubert@iram.es>
>
> Acked-by: Lennert Buytenhek <buytenh@marvell.com>
Applied, thanks everyone.
^ permalink raw reply
* Re: [PATCH 1/3] powerpc, Makefile: Make it possible to safely select CONFIG_FRAME_POINTER
From: Benjamin Herrenschmidt @ 2009-05-18 4:14 UTC (permalink / raw)
To: Steven Rostedt
Cc: linux-kernel, linuxppc-dev, Paul Mackerras, Ingo Molnar,
Sam Ravnborg
In-Reply-To: <1241565080.11379.14.camel@localhost.localdomain>
On Tue, 2009-05-05 at 19:11 -0400, Steven Rostedt wrote:
> OK, so what's the status with this patch series?
>
> I don't want to pull it in unless I have an ack from Sam, and now
> there's issues with having -fno-omit-frame-pointer. Should we add a
> patch instead that simply removes that?
>
> If we eliminate the -fno-omit-frame-pointer, would that solve the PPC
> problem? And would it cause any other issues with other archs?
Well, the patch looks fine to me to be honest I'm not sure what the
complaint is above...
IE. On arch that define the new HAVE_NORMAL_FRAME_POINTER (let's just
call it HAVE_IMPLIED_FRAME_POINTER then :-) we just don't do
-fno-omit-frame-pointer and avoid triggering the bug...
Segher, what are we missing here ?
Ben.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox