summaryrefslogtreecommitdiffstats
path: root/drivers/edac/e752x_edac.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/edac/e752x_edac.c')
-rw-r--r--drivers/edac/e752x_edac.c117
1 files changed, 109 insertions, 8 deletions
diff --git a/drivers/edac/e752x_edac.c b/drivers/edac/e752x_edac.c
index d205d49..243e9aa 100644
--- a/drivers/edac/e752x_edac.c
+++ b/drivers/edac/e752x_edac.c
@@ -75,6 +75,14 @@ static struct edac_pci_ctl_info *e752x_pci;
#define E752X_NR_CSROWS 8 /* number of csrows */
/* E752X register addresses - device 0 function 0 */
+#define E752X_MCHSCRB 0x52 /* Memory Scrub register (16b) */
+ /*
+ * 6:5 Scrub Completion Count
+ * 3:2 Scrub Rate (i3100 only)
+ * 01=fast 10=normal
+ * 1:0 Scrub Mode enable
+ * 00=off 10=on
+ */
#define E752X_DRB 0x60 /* DRAM row boundary register (8b) */
#define E752X_DRA 0x70 /* DRAM row attribute register (8b) */
/*
@@ -240,6 +248,41 @@ static const struct e752x_dev_info e752x_devs[] = {
.ctl_name = "3100"},
};
+/* Valid scrub rates for the e752x/3100 hardware memory scrubber. We
+ * map the scrubbing bandwidth to a hardware register value. The 'set'
+ * operation finds the 'matching or higher value'. Note that scrubbing
+ * on the e752x can only be enabled/disabled. The 3100 supports
+ * a normal and fast mode.
+ */
+
+#define SDRATE_EOT 0xFFFFFFFF
+
+struct scrubrate {
+ u32 bandwidth; /* bandwidth consumed by scrubbing in bytes/sec */
+ u16 scrubval; /* register value for scrub rate */
+};
+
+/* Rate below assumes same performance as i3100 using PC3200 DDR2 in
+ * normal mode. e752x bridges don't support choosing normal or fast mode,
+ * so the scrubbing bandwidth value isn't all that important - scrubbing is
+ * either on or off.
+ */
+static const struct scrubrate scrubrates_e752x[] = {
+ {0, 0x00}, /* Scrubbing Off */
+ {500000, 0x02}, /* Scrubbing On */
+ {SDRATE_EOT, 0x00} /* End of Table */
+};
+
+/* Fast mode: 2 GByte PC3200 DDR2 scrubbed in 33s = 63161283 bytes/s
+ * Normal mode: 125 (32000 / 256) times slower than fast mode.
+ */
+static const struct scrubrate scrubrates_i3100[] = {
+ {0, 0x00}, /* Scrubbing Off */
+ {500000, 0x0a}, /* Normal mode - 32k clocks */
+ {62500000, 0x06}, /* Fast mode - 256 clocks */
+ {SDRATE_EOT, 0x00} /* End of Table */
+};
+
static unsigned long ctl_page_to_phys(struct mem_ctl_info *mci,
unsigned long page)
{
@@ -915,6 +958,68 @@ static void e752x_check(struct mem_ctl_info *mci)
e752x_process_error_info(mci, &info, 1);
}
+/* Program byte/sec bandwidth scrub rate to hardware */
+static int set_sdram_scrub_rate(struct mem_ctl_info *mci, u32 *new_bw)
+{
+ const struct scrubrate *scrubrates;
+ struct e752x_pvt *pvt = (struct e752x_pvt *) mci->pvt_info;
+ struct pci_dev *pdev = pvt->dev_d0f0;
+ int i;
+
+ if (pvt->dev_info->ctl_dev == PCI_DEVICE_ID_INTEL_3100_0)
+ scrubrates = scrubrates_i3100;
+ else
+ scrubrates = scrubrates_e752x;
+
+ /* Translate the desired scrub rate to a e752x/3100 register value.
+ * Search for the bandwidth that is equal or greater than the
+ * desired rate and program the cooresponding register value.
+ */
+ for (i = 0; scrubrates[i].bandwidth != SDRATE_EOT; i++)
+ if (scrubrates[i].bandwidth >= *new_bw)
+ break;
+
+ if (scrubrates[i].bandwidth == SDRATE_EOT)
+ return -1;
+
+ pci_write_config_word(pdev, E752X_MCHSCRB, scrubrates[i].scrubval);
+
+ return 0;
+}
+
+/* Convert current scrub rate value into byte/sec bandwidth */
+static int get_sdram_scrub_rate(struct mem_ctl_info *mci, u32 *bw)
+{
+ const struct scrubrate *scrubrates;
+ struct e752x_pvt *pvt = (struct e752x_pvt *) mci->pvt_info;
+ struct pci_dev *pdev = pvt->dev_d0f0;
+ u16 scrubval;
+ int i;
+
+ if (pvt->dev_info->ctl_dev == PCI_DEVICE_ID_INTEL_3100_0)
+ scrubrates = scrubrates_i3100;
+ else
+ scrubrates = scrubrates_e752x;
+
+ /* Find the bandwidth matching the memory scrubber configuration */
+ pci_read_config_word(pdev, E752X_MCHSCRB, &scrubval);
+ scrubval = scrubval & 0x0f;
+
+ for (i = 0; scrubrates[i].bandwidth != SDRATE_EOT; i++)
+ if (scrubrates[i].scrubval == scrubval)
+ break;
+
+ if (scrubrates[i].bandwidth == SDRATE_EOT) {
+ e752x_printk(KERN_WARNING,
+ "Invalid sdram scrub control value: 0x%x\n", scrubval);
+ return -1;
+ }
+
+ *bw = scrubrates[i].bandwidth;
+
+ return 0;
+}
+
/* Return 1 if dual channel mode is active. Else return 0. */
static inline int dual_channel_active(u16 ddrcsr)
{
@@ -1073,10 +1178,7 @@ fail:
/* Setup system bus parity mask register.
* Sysbus parity supported on:
- * e7320/e7520/e7525 + Xeon
- * i3100 + Xeon/Celeron
- * Sysbus parity not supported on:
- * i3100 + Pentium M/Celeron M/Core Duo/Core2 Duo
+ * e7320/e7520/e7525 + Xeon
*/
static void e752x_init_sysbus_parity_mask(struct e752x_pvt *pvt)
{
@@ -1087,10 +1189,7 @@ static void e752x_init_sysbus_parity_mask(struct e752x_pvt *pvt)
/* Allow module parameter override, else see if CPU supports parity */
if (sysbus_parity != -1) {
enable = sysbus_parity;
- } else if (cpu_id[0] &&
- ((strstr(cpu_id, "Pentium") && strstr(cpu_id, " M ")) ||
- (strstr(cpu_id, "Celeron") && strstr(cpu_id, " M ")) ||
- (strstr(cpu_id, "Core") && strstr(cpu_id, "Duo")))) {
+ } else if (cpu_id[0] && !strstr(cpu_id, "Xeon")) {
e752x_printk(KERN_INFO, "System Bus Parity not "
"supported by CPU, disabling\n");
enable = 0;
@@ -1187,6 +1286,8 @@ static int e752x_probe1(struct pci_dev *pdev, int dev_idx)
mci->dev_name = pci_name(pdev);
mci->edac_check = e752x_check;
mci->ctl_page_to_phys = ctl_page_to_phys;
+ mci->set_sdram_scrub_rate = set_sdram_scrub_rate;
+ mci->get_sdram_scrub_rate = get_sdram_scrub_rate;
/* set the map type. 1 = normal, 0 = reversed
* Must be set before e752x_init_csrows in case csrow mapping
OpenPOWER on IntegriCloud