<p>Arthur Heymans has uploaded this change for <strong>review</strong>.</p><p><a href="https://review.coreboot.org/22329">View Change</a></p><pre style="font-family: monospace,monospace; white-space: pre-wrap;">[WIP/UNTESTED]nb/intel/x4x: Implement both read and write training<br><br>Code looks bad and does weird things over ranks. Should probably do<br>things like gm45 and train one lane over all available ranks.<br><br>Change-Id: Iacdc63b91b4705d1a80437314bfe55385ea5b6c1<br>Signed-off-by: Arthur Heymans <arthur@aheymans.xyz><br>---<br>M src/northbridge/intel/x4x/Makefile.inc<br>A src/northbridge/intel/x4x/dq_dqs.c<br>M src/northbridge/intel/x4x/raminit_ddr2.c<br>M src/northbridge/intel/x4x/x4x.h<br>4 files changed, 629 insertions(+), 5 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://review.coreboot.org:29418/coreboot refs/changes/29/22329/1</pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/src/northbridge/intel/x4x/Makefile.inc b/src/northbridge/intel/x4x/Makefile.inc<br>index 0e5a4a3..20db630 100644<br>--- a/src/northbridge/intel/x4x/Makefile.inc<br>+++ b/src/northbridge/intel/x4x/Makefile.inc<br>@@ -22,6 +22,7 @@<br> romstage-y += ram_calc.c<br> romstage-y += rcven.c<br> romstage-y += raminit_tables.c<br>+romstage-y += dq_dqs.c<br> <br> ramstage-y += acpi.c<br> ramstage-y += ram_calc.c<br>diff --git a/src/northbridge/intel/x4x/dq_dqs.c b/src/northbridge/intel/x4x/dq_dqs.c<br>new file mode 100644<br>index 0000000..6af88e0<br>--- /dev/null<br>+++ b/src/northbridge/intel/x4x/dq_dqs.c<br>@@ -0,0 +1,617 @@<br>+/*<br>+ * This file is part of the coreboot project.<br>+ *<br>+ * Copyright (C) 2017 Arthur Heymans <arthur@aheymans.xyz><br>+ *<br>+ * This program is free software; you can redistribute it and/or<br>+ * modify it under the terms of the GNU General Public License as<br>+ * published by the Free Software Foundation; either version 2 of<br>+ * the License, or (at your option) any later version.<br>+ *<br>+ * This program is distributed in the hope that it will be useful,<br>+ * but WITHOUT ANY WARRANTY; without even the implied warranty of<br>+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the<br>+ * GNU General Public License for more details.<br>+ */<br>+<br>+#include <arch/io.h><br>+#include <console/console.h><br>+#include <stdint.h><br>+#include <string.h><br>+#include "x4x.h"<br>+#include "iomap.h"<br>+<br>+static void print_dll_setting(const struct dll_setting *dll_setting,<br>+                      u8 default_verbose)<br>+{<br>+      u8 debug_level;<br>+      if (default_verbose)<br>+         debug_level = BIOS_DEBUG;<br>+    else<br>+         debug_level = RAM_DEBUG;<br>+<br>+  printk(debug_level, "%d.%d.%d.%d:%d.%d\n", dll_setting->coarse,<br>+         dll_setting->clk_delay, dll_setting->tap,<br>+              dll_setting->pi, dll_setting->db_en,<br>+           dll_setting->db_sel);<br>+}<br>+<br>+static void set_db(struct sysinfo *s, struct dll_setting *dq_dqs_setting)<br>+{<br>+      u8 db_tap, db_pi;<br>+<br>+ switch (s->selected_timings.mem_clk) {<br>+    default:<br>+     case MEM_CLOCK_800MHz:<br>+               db_tap = 0xa3;<br>+               db_pi = 0x32;<br>+                break;<br>+       case MEM_CLOCK_1066MHz:<br>+              db_tap = 0x82;<br>+               db_pi = 0x76;<br>+                break;<br>+       case MEM_CLOCK_1333MHz:<br>+              db_tap = 0xb3;<br>+               db_pi = 0x46;<br>+                break;<br>+       }<br>+<br>+ if (dq_dqs_setting->tap < (db_tap & 0xf)) {<br>+                dq_dqs_setting->db_en = 1;<br>+                dq_dqs_setting->db_sel = 1;<br>+       } else if ((dq_dqs_setting->tap == (db_tap & 0xf))<br>+                    && (dq_dqs_setting->pi < (db_pi & 0xf))) {<br>+         dq_dqs_setting->db_en = 1;<br>+                dq_dqs_setting->db_sel = 1;<br>+       } else if (dq_dqs_setting->tap < (db_tap >> 4)) {<br>+                dq_dqs_setting->db_en = 0;<br>+                dq_dqs_setting->db_sel = 0;<br>+       } else if ((dq_dqs_setting->tap == (db_tap >> 4))<br>+                   && (dq_dqs_setting->pi < (db_pi >> 4))) {<br>+                dq_dqs_setting->db_en = 0;<br>+                dq_dqs_setting->db_sel = 0;<br>+       } else {<br>+             dq_dqs_setting->db_en = 1;<br>+                dq_dqs_setting->db_sel = 0;<br>+       }<br>+}<br>+<br>+const static u8 max_tap[3] = {12, 10, 13};<br>+<br>+static int increment_dq_dqs(struct sysinfo *s, struct dll_setting *dq_dqs_setting)<br>+{<br>+    u8 max_tap_val = max_tap[s->selected_timings.mem_clk<br>+                              - MEM_CLOCK_800MHz];<br>+ /* PI */<br>+     if (dq_dqs_setting->pi < 6) {<br>+          dq_dqs_setting->pi += 1;<br>+  } else if (dq_dqs_setting->tap < max_tap_val) {<br>+                dq_dqs_setting->pi = 0;<br>+           dq_dqs_setting->tap += 1;<br>+ } else if (dq_dqs_setting->clk_delay < 2) {<br>+            dq_dqs_setting->pi = 0;<br>+           dq_dqs_setting->tap = 0;<br>+          dq_dqs_setting->clk_delay += 1;<br>+   } else if (dq_dqs_setting->coarse < 1) {<br>+               dq_dqs_setting->pi = 0;<br>+           dq_dqs_setting->tap = 0;<br>+          dq_dqs_setting->clk_delay = 0;<br>+            dq_dqs_setting->coarse += 1;<br>+      } else {<br>+             return 1;<br>+    }<br>+    set_db(s, dq_dqs_setting);<br>+   return 0;<br>+}<br>+<br>+#define WT_PATTERN_SIZE 80<br>+<br>+static const u32 write_training_schedule[WT_PATTERN_SIZE] = {<br>+     0xffffffff, 0x00000000, 0xffffffff, 0x00000000,<br>+      0xffffffff, 0x00000000, 0xffffffff, 0x00000000,<br>+      0xffffffff, 0x00000000, 0xffffffff, 0x00000000,<br>+      0xffffffff, 0x00000000, 0xffffffff, 0x00000000,<br>+      0xefefefef, 0x10101010, 0xefefefef, 0x10101010,<br>+      0xefefefef, 0x10101010, 0xefefefef, 0x10101010,<br>+      0xefefefef, 0x10101010, 0xefefefef, 0x10101010,<br>+      0xefefefef, 0x10101010, 0xefefefef, 0x10101010,<br>+      0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010,<br>+      0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010,<br>+      0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010,<br>+      0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010,<br>+      0x03030303, 0x04040404, 0x09090909, 0x10101010,<br>+      0x21212121, 0x40404040, 0x81818181, 0x00000000,<br>+      0x03030303, 0x04040404, 0x09090909, 0x10101010,<br>+      0x21212121, 0x40404040, 0x81818181, 0x00000000,<br>+      0xfdfdfdfd, 0xfafafafa, 0xf7f7f7f7, 0xeeeeeeee,<br>+      0xdfdfdfdf, 0xbebebebe, 0x7f7f7f7f, 0xfefefefe,<br>+      0xfdfdfdfd, 0xfafafafa, 0xf7f7f7f7, 0xeeeeeeee,<br>+      0xdfdfdfdf, 0xbebebebe, 0x7f7f7f7f, 0xfefefefe,<br>+};<br>+<br>+static int test_dq_aligned(struct sysinfo *s, u32 address)<br>+{<br>+     u8 error_found = 0, lane, count;<br>+     u8 data[8];<br>+  u16 i;<br>+       u32 content;<br>+<br>+      for (i = 0; i < WT_PATTERN_SIZE; i++) {<br>+           for (count = 0; count < WT_PATTERN_SIZE; count++) {<br>+                       if ((count % 16) == 0)<br>+                               MCHBAR32(0xf90) = 1;<br>+                 content = write_training_schedule[count];<br>+                    write32((u32 *)address + 8 * count, content);<br>+                        write32((u32 *)address + 8 * count + 4, content);<br>+            }<br>+<br>+         write32(&data[0], read32((u32 *)address + 8 * i));<br>+               write32(&data[4], read32((u32 *)address + 8 * i + 4));<br>+           for (lane = 0; lane < 8; lane++) {<br>+                        if (data[lane] != (write_training_schedule[i] & 0xff))<br>+                           error_found |= (1 << lane);<br>+            }<br>+            /* No need to continue now */<br>+                if (error_found == 0xff)<br>+                     return 0xff;<br>+ }<br>+    return error_found;<br>+}<br>+<br>+enum training_modes {<br>+   SUCCEEDING = 0,<br>+      FAILING = 1<br>+};<br>+#define CONSISTENCY 10<br>+<br>+/*<br>+ * This function finds either failing or succeeding writes by increasing DQ.<br>+ * When it has found a failing or succeeding setting it will increase DQ<br>+ * another 10 times to make sure the result is consistent.<br>+ * This means that the middle between failing and succeeding writes is shifted<br>+ * by 9 steps, which need to be substracted later.<br>+ */<br>+static int find_dq_limit(struct sysinfo *s, u8 channel, u32 address,<br>+                  struct dll_setting *dq_setting, u8 *dq_lim,<br>+                  u8 direction)<br>+{<br>+    int status;<br>+  u8 lane_passes[8] = { };<br>+     u8 sample = 0xff;<br>+    u8 lane;<br>+     u8 lane_err;<br>+<br>+      for (lane = 0; lane < 8; lane++)<br>+          dqset(channel, lane, &dq_setting[lane]);<br>+<br>+      while(sample) {<br>+              status = 0;<br>+          /* TODO: This is probably more readable if done one lane at the time */<br>+              lane_err = test_dq_aligned(s, address);<br>+              lane_err ^= 0xff * direction;<br>+                for (lane = 0; lane < 8; lane++) {<br>+                        if (lane_err & (1 << lane)) {<br>+                              /* reuse function for DQ DLL settings */<br>+                             status = increment_dq_dqs(s, &dq_setting[lane]);<br>+                         dqset(channel, lane, &dq_setting[lane]);<br>+                         dq_lim[lane]++;<br>+                      } else if (lane_passes[lane] < CONSISTENCY) {<br>+                             status = increment_dq_dqs(s, &dq_setting[lane]);<br>+                         dqset(channel, lane, &dq_setting[lane]);<br>+                         dq_lim[lane]++;<br>+                              lane_passes[lane]++;<br>+                 } else if (lane_passes[lane] == CONSISTENCY) {<br>+                               sample &= ~(1 << lane);<br>+                    }<br>+            }<br>+            if (status) {<br>+                        if (direction == 0) {<br>+                                printk(BIOS_ERR,<br>+                                     "Could not find good Write training settings\n");<br>+                          return 1;<br>+                    } else {<br>+                             break;<br>+                       }<br>+            }<br>+    }<br>+    return 0;<br>+}<br>+<br>+/*<br>+ * Increase DQ until writes succeed, then further increase DQ until it fails.<br>+ * Use the middle between this working lower limit and this failing upper<br>+ * limit.<br>+ * INVESTIGATE: why not taking most extreme values over each rank?<br>+ */<br>+int do_write_training(struct sysinfo *s)<br>+{<br>+      int i;<br>+       u8 channel, lane;<br>+    u32 address;<br>+ u8 dq_lower_r0[8];<br>+   u8 dq_upper_r0[8];<br>+   u8 dq_lower_r2[8];<br>+   u8 dq_upper_r2[8];<br>+   u8 dq_center[8];<br>+     struct dll_setting dq_setting[8];<br>+    u8 dq_average;<br>+       u32 dq_absolute;<br>+<br>+  printk(BIOS_DEBUG, "Starting DQ write training\n");<br>+<br>+     FOR_EACH_POPULATED_CHANNEL(s->dimms, channel) {<br>+           printk(BIOS_DEBUG, "Doing DQ write training on CH%d\n", channel);<br>+          address = 0x20000000 * channel;<br>+              for (i = 0; (i < RANKS_PER_CHANNEL) &&<br>+                         !RANK_IS_POPULATED(s->dimms, channel, i); i++)<br>+                       address += 128 * MiB;<br>+<br>+             dq_average = 0;<br>+              dq_absolute = 0;<br>+             memset(dq_lower_r0, 0, sizeof(dq_lower_r0));<br>+         memset(dq_lower_r2, 0, sizeof(dq_lower_r2));<br>+<br>+              memset(dq_center, 0, sizeof(dq_center));<br>+             for (lane = 0; lane < 8; lane++) {<br>+                        /* Start from DQS settings */<br>+                        s->dq_settings[channel][lane] =<br>+                           s->dqs_settings[channel][lane];<br>+                   dq_setting[lane] = s->dqs_settings[channel][lane];<br>+                }<br>+<br>+         if (find_dq_limit(s, channel, address, dq_setting, dq_lower_r0,<br>+                                      SUCCEEDING)) {<br>+                       printk(BIOS_CRIT,<br>+                            "Could not find working lower limit DQ setting\n");<br>+                        return -1;<br>+           }<br>+            printk(RAM_DEBUG, "Lower Limit for DQ on rank 0:\n");<br>+              for (lane = 0; lane < 8; lane++) {<br>+                        printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",<br>+                           channel, lane, dq_lower_r0[lane]);<br>+           }<br>+<br>+         memcpy(dq_upper_r0, dq_lower_r0, sizeof(dq_upper_r0));<br>+               if (find_dq_limit(s, channel, address, dq_setting, dq_upper_r0,<br>+                                      FAILING)) {<br>+                  printk(BIOS_CRIT,<br>+                            "Could not find failing upper limit DQ setting\n");<br>+                        return -1;<br>+           }<br>+<br>+         printk(RAM_DEBUG, "Upper Limit for DQ on rank 0:\n");<br>+              for (lane = 0; lane < 8; lane++) {<br>+                        printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",<br>+                           channel, lane, dq_upper_r0[lane]);<br>+           }<br>+<br>+         if (RANK_IS_POPULATED(s->dimms, channel, 0)<br>+                       && RANK_IS_POPULATED(s->dimms, channel, 2)) {<br>+                     address += 256 * MiB;<br>+<br>+                     if (find_dq_limit(s, channel, address, dq_setting,<br>+                                           dq_lower_r2, SUCCEEDING)) {<br>+                          printk(BIOS_CRIT, "Could not find working lower limit DQ setting\n");<br>+                              return -1;<br>+                   }<br>+                    printk(RAM_DEBUG, "Lower Limit for DQ on rank 2:\n");<br>+                      for (lane = 0; lane < 8; lane++) {<br>+                                printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",<br>+                                   channel, lane, dq_lower_r2[lane]);<br>+                   }<br>+<br>+                 memcpy(dq_upper_r2, dq_lower_r2, sizeof(dq_upper_r2));<br>+                       if (find_dq_limit(s, channel, address, dq_setting,<br>+                                           dq_upper_r2, FAILING)) {<br>+                             printk(BIOS_CRIT, "Could not find failing upper limit DQ setting\n");<br>+                              return -1;<br>+                   }<br>+                    printk(RAM_DEBUG, "Upper Limit for DQ on rank 2:\n");<br>+                      for (lane = 0; lane < 8; lane++) {<br>+                                printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",<br>+                                   channel, lane, dq_upper_r2[lane]);<br>+                   }<br>+<br>+                 for (lane = 0; lane < 8; lane++) {<br>+                                dq_upper_r0[lane] = MIN(dq_upper_r0[lane],<br>+                                                   dq_upper_r2[lane]);<br>+                          dq_lower_r0[lane] = MAX(dq_lower_r0[lane],<br>+                                                   dq_lower_r2[lane]);<br>+                  }<br>+            } /* If Rank 2 is present */<br>+<br>+              for (lane = 0; lane < 8; lane++) {<br>+                        dq_lower_r0[lane] -= CONSISTENCY - 1;<br>+                        dq_upper_r0[lane] -= CONSISTENCY - 1;<br>+                }<br>+<br>+         for (lane = 0; lane < 8; lane++)<br>+                  dq_center[lane] = (dq_upper_r0[lane]<br>+                                 + dq_lower_r0[lane]) / 2;<br>+<br>+         printk(RAM_DEBUG, "Centered values for DQ DLL:\n");<br>+                for (lane = 0; lane < 8; lane++) {<br>+                        printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",<br>+                           channel, lane, dq_center[lane]);<br>+             }<br>+<br>+         /* Reset DQ DLL settings and increment with centered value*/<br>+         printk(BIOS_DEBUG, "Final DQ timings on CH%d\n", channel);<br>+         for (lane = 0; lane < 8; lane++) {<br>+                        for (i = 0; i < dq_center[lane]; i++)<br>+                             increment_dq_dqs(s, &s->dq_settings[channel][lane]);<br>+                  printk(BIOS_DEBUG, "\tlane%d: ", lane);<br>+                    print_dll_setting(&s->dq_settings[channel][lane], 1);<br>+                 dqset(channel, lane, &s->dq_settings[channel][lane]);<br>+         }<br>+    }<br>+    printk(BIOS_DEBUG, "Done DQ write training\n");<br>+    return 0;<br>+}<br>+<br>+#define RT_PATTERN_SIZE 40<br>+<br>+static const u32 read_training_schedule[RT_PATTERN_SIZE] = {<br>+      0xffffffff, 0x00000000, 0xffffffff, 0x00000000,<br>+      0xffffffff, 0x00000000, 0xffffffff, 0x00000000,<br>+      0xefefefef, 0x10101010, 0xefefefef, 0x10101010,<br>+      0xefefefef, 0x10101010, 0xefefefef, 0x10101010,<br>+      0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010,<br>+      0xefefefef, 0xeeeeeeee, 0x11111111, 0x10101010,<br>+      0x03030303, 0x04040404, 0x09090909, 0x10101010,<br>+      0x21212121, 0x40404040, 0x81818181, 0x00000000,<br>+      0xfdfdfdfd, 0xfafafafa, 0xf7f7f7f7, 0xeeeeeeee,<br>+      0xdfdfdfdf, 0xbebebebe, 0x7f7f7f7f, 0xfefefefe<br>+};<br>+<br>+static void rt_set_dqs(u8 channel, u8 lane, u8 rank, struct rt_dqs_setting *dqs_setting)<br>+{<br>+        u8 saved_tap = MCHBAR16(0x540 + 0x400 * channel + lane * 4);<br>+ u8 saved_pi = MCHBAR16(0x542 + 0x400 * channel + lane * 4);<br>+  printk(RAM_SPEW, "RT DQS: ch%d, L%d, %d.%d\n", channel, lane,<br>+              dqs_setting->tap,<br>+         dqs_setting->pi);<br>+<br>+      saved_tap &= ~(0xf << (rank * 4));<br>+ saved_tap |= dqs_setting->tap << (rank * 4);<br>+        MCHBAR16(0x540 + 0x400 * channel + lane * 4) = saved_tap;<br>+<br>+ saved_pi &= ~(0x7 << (rank * 3));<br>+  saved_pi |= dqs_setting->pi << (rank * 4);<br>+  MCHBAR16(0x542 + 0x400 * channel + lane * 4) = saved_pi;<br>+}<br>+<br>+static int rt_increment_dqs(struct rt_dqs_setting *setting)<br>+{<br>+    if (setting->pi < 7) {<br>+         setting->pi++;<br>+    } else if (setting->tap < 14) {<br>+                setting->pi = 0;<br>+          setting->tap++;<br>+   } else {<br>+             return -1;<br>+   }<br>+    return 0;<br>+}<br>+<br>+static u8 test_dqs_aligned(struct sysinfo *s, u8 channel, u32 address)<br>+{<br>+        u8 error_lane = 0;<br>+   u8 data8[8];<br>+ int i, lane;<br>+<br>+      for (i = 0; i < RT_PATTERN_SIZE; i++) {<br>+           write32(&data8[0], read32((u32 *)address + i * 8));<br>+              write32(&data8[4], read32((u32 *)address + i * 8 + 4));<br>+          for (lane = 0; lane < 8; lane++) {<br>+                        if (data8[lane] != (read_training_schedule[i] & 0xff))<br>+                           error_lane |= (1 << lane);<br>+             }<br>+            /* No need to continue now */<br>+                if (error_lane == 0xff)<br>+                      break;<br>+<br>+    }<br>+    return error_lane;<br>+}<br>+<br>+static int rt_find_dqs_limit(struct sysinfo *s, u8 channel, u32 address,<br>+                 struct rt_dqs_setting *dqs_setting, u8 *dqs_lim,<br>+                     u8 direction)<br>+{<br>+    int lane;<br>+    u8 sample = 0xff, lane_err;<br>+<br>+       for (lane = 0; lane < 8; lane++)<br>+          rt_set_dqs(channel, lane, 0,  &dqs_setting[lane]);<br>+<br>+    while(sample) {<br>+              /* TODO: This is probably more readable if done one lane at the time */<br>+              lane_err = test_dqs_aligned(s, channel, address);<br>+            lane_err ^= 0xff * direction;<br>+                for (lane = 0; lane < 8; lane++) {<br>+                        /* Checking lanes that have already been done is a good idea */<br>+                      /* since those can be found bad again. */<br>+                    if (lane_err & (1 << lane)) {<br>+                              if (rt_increment_dqs(&dqs_setting[lane])) {<br>+                                      if (direction == SUCCEEDING) {<br>+                                               printk(BIOS_CRIT,<br>+                                                    "Could not find RT DQS setting\n");<br>+                                                return -1;<br>+                                   } else {<br>+                                             sample &= ~(1 << lane);<br>+                                            continue;<br>+                                    }<br>+                            }<br>+                            dqs_lim[lane]++;<br>+                             rt_set_dqs(channel, lane, 0,<br>+                                 &dqs_setting[lane]);<br>+                     } else {<br>+                             sample &= ~(1 << lane);<br>+                    }<br>+            }<br>+    }<br>+    return 0;<br>+}<br>+<br>+#define RT_LOOPS 3<br>+<br>+/*<br>+ * Increase DQS until read succeed, then further increase DQS until it fails.<br>+ * Use the middle between this working lower limit and this failing upper<br>+ * limit.<br>+ * To improve statistics this is done RT_LOOPS amount of timings, while<br>+ * additioning the centered values to some saved values. At the end the<br>+ * saved values are divided by RT_LOOPS.<br>+ *<br>+ * TO INVESTIGATE: vendor trains rank 0 and rank 2 and uses the most<br>+ * extreme results to compute the middle 'eye' value, which is then<br>+ * used for all ranks.<br>+ * Try performing the training and programming the result on each individual<br>+ * rank.<br>+ */<br>+int do_read_training(struct sysinfo *s)<br>+{<br>+        int loop, channel, i, lane, rank;<br>+    u32 address, content;<br>+        u8 dqs_lower_r0[8];<br>+  u8 dqs_upper_r0[8];<br>+  u8 dqs_lower_r2[8];<br>+  u8 dqs_upper_r2[8];<br>+  u8 dqs_center[8];<br>+    u16 saved_dqs[2][8] = { };<br>+<br>+        struct rt_dqs_setting dqs_setting[8];<br>+<br>+     printk(BIOS_DEBUG, "Starting DQS read training\n");<br>+        memset(s->rt_dqs, 0, sizeof(s->rt_dqs));<br>+<br>+    for (loop = 0; loop < RT_LOOPS; loop++) {<br>+         FOR_EACH_POPULATED_CHANNEL(s->dimms, channel) {<br>+                   printk(BIOS_DEBUG, "Doing DQS write training on CH%d, loop%d\n",<br>+                           channel, loop);<br>+                      address = 0x20000000 * channel;<br>+                      for (i = 0; !RANK_IS_POPULATED(s->dimms, channel, i)<br>+                                   && i < RANKS_PER_CHANNEL; i++)<br>+                               address += 128 * MiB;<br>+<br>+                     /* Write pattern to strobe address*/<br>+                 for (i = 0; i < RT_PATTERN_SIZE; i++) {<br>+                           content = read_training_schedule[i];<br>+                         write32((u32 *)address + 8 * i, content);<br>+                            write32((u32 *)address + 8 * i + 4, content);<br>+                        }<br>+<br>+                 memset(dqs_lower_r0, 0, sizeof(dqs_lower_r0));<br>+                       memset(dqs_lower_r2, 0, sizeof(dqs_lower_r2));<br>+                       memset(dqs_setting, 0, sizeof(dqs_setting));<br>+<br>+                      if (rt_find_dqs_limit(s, channel, address, dqs_setting,<br>+                                              dqs_lower_r0, SUCCEEDING)) {<br>+                         printk(BIOS_CRIT,<br>+                                    "Could not find working lower limit DQS setting\n");<br>+                               return -1;<br>+                   }<br>+                    printk(RAM_DEBUG, "Lower Limit for DQS on rank 0:\n");<br>+                     for (lane = 0; lane < 8; lane++) {<br>+                                printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",<br>+                                   channel, lane, dqs_lower_r0[lane]);<br>+                  }<br>+<br>+                 memcpy(dqs_upper_r0, dqs_lower_r0,<br>+                           sizeof(dqs_upper_r0));<br>+                       if (rt_find_dqs_limit(s, channel, address, dqs_setting,<br>+                                              dqs_upper_r0, FAILING)) {<br>+                            printk(BIOS_CRIT,<br>+                                    "Could not find failing upper limit DQ setting\n");<br>+                                return -1;<br>+                   }<br>+<br>+                 printk(RAM_DEBUG, "Upper Limit for DQS on rank 0:\n");<br>+                     for (lane = 0; lane < 8; lane++) {<br>+                                printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",<br>+                           channel, lane, dqs_upper_r0[lane]);<br>+                  }<br>+<br>+                 if (RANK_IS_POPULATED(s->dimms, channel, 0)<br>+                               && RANK_IS_POPULATED(s->dimms, channel, 2)) {<br>+                             address += 256 * MiB;<br>+<br>+                             memset(dqs_setting, 0 , sizeof(dqs_setting));<br>+<br>+                             if (rt_find_dqs_limit(s, channel, address,<br>+                                                   dqs_setting,<br>+                                                 dqs_lower_r2,<br>+                                                        SUCCEEDING)) {<br>+                                       printk(BIOS_CRIT,<br>+                                            "Could not find working lower limit DQS setting\n");<br>+                                       return -1;<br>+                           }<br>+                            printk(RAM_DEBUG, "Lower Limit for DQS on rank 2:\n");<br>+                             for (lane = 0; lane < 8; lane++) {<br>+                                        printk(RAM_DEBUG, " ch%d, lane %d, #steps %d\n",<br>+                                           channel, lane, dqs_lower_r2[lane]);<br>+                          }<br>+<br>+                         memcpy(dqs_upper_r2, dqs_lower_r2,<br>+                                   sizeof(dqs_upper_r2));<br>+                               if (rt_find_dqs_limit(s, channel, address,<br>+                                                   dqs_setting,<br>+                                                 dqs_upper_r2,<br>+                                                        FAILING)) {<br>+                                  printk(BIOS_CRIT,<br>+                                            "Could not find failing upper limit DQS setting\n");<br>+                                       return -1;<br>+                           }<br>+                            printk(RAM_DEBUG,<br>+                                    "Upper Limit for DQS on rank 2:\n");<br>+                               for (lane = 0; lane < 8; lane++) {<br>+                                        printk(RAM_DEBUG,<br>+                                            " ch%d, lane %d, #steps %d\n",<br>+                                             channel, lane, dqs_upper_r2[lane]);<br>+                          }<br>+<br>+                         for (lane = 0; lane < 8; lane++) {<br>+                                        dqs_upper_r0[lane] =<br>+                                         MIN(dqs_upper_r0[lane],<br>+                                                      dqs_upper_r2[lane]);<br>+                                 dqs_lower_r0[lane] =<br>+                                         MAX(dqs_lower_r0[lane],<br>+                                                      dqs_lower_r2[lane]);<br>+                         }<br>+                    } /* end If Rank 2 is present */<br>+<br>+                  for (lane = 0; lane < 8; lane++) {<br>+                                dqs_center[lane] = (dqs_upper_r0[lane]<br>+                                               + dqs_lower_r0[lane]) / 2;<br>+                           saved_dqs[channel][lane] += dqs_center[lane];<br>+                        }<br>+            } /* END FOR_EACH_POPULATED_CHANNEL */<br>+       } /* end RT_LOOPS */<br>+<br>+      /* Set all ranks at the same value as the first rank */<br>+      FOR_EACH_POPULATED_CHANNEL(s->dimms, channel) {<br>+           printk(BIOS_DEBUG, "Final timings on CH%d\n", channel);<br>+            for (lane = 0; lane < 8; lane++) {<br>+                        saved_dqs[channel][lane] /= RT_LOOPS;<br>+                        while (saved_dqs[channel][lane]--) { /* check for overflow ? */<br>+                              FOR_EACH_POPULATED_RANK_IN_CHANNEL(s->dimms,<br>+                                                              channel, rank) {<br>+                                     rt_increment_dqs(&s->rt_dqs[channel][rank][lane]);<br>+                            }<br>+                    }<br>+                    FOR_EACH_POPULATED_RANK_IN_CHANNEL(s->dimms, channel, rank)<br>+                               rt_set_dqs(channel, lane, rank,<br>+                                      &s->rt_dqs[channel][rank][lane]);<br>+             }<br>+            FOR_EACH_POPULATED_RANK_IN_CHANNEL(s->dimms, channel, rank) {<br>+                     for (lane = 0; lane < 8; lane++)<br>+                          printk(BIOS_DEBUG, "\tr%dlane%d: %d.%d\n",<br>+                                 rank, lane,<br>+                                  s->rt_dqs[channel][rank][lane].tap,<br>+                                       s->rt_dqs[channel][rank][lane].pi);<br>+               }<br>+    }<br>+    printk(BIOS_DEBUG, "Done DQS read training\n");<br>+    return 0;<br>+}<br>diff --git a/src/northbridge/intel/x4x/raminit_ddr2.c b/src/northbridge/intel/x4x/raminit_ddr2.c<br>index 90912ad..7f54669 100644<br>--- a/src/northbridge/intel/x4x/raminit_ddr2.c<br>+++ b/src/northbridge/intel/x4x/raminit_ddr2.c<br>@@ -292,7 +292,7 @@<br>  * All finer DQ and DQS DLL settings are set to the same value<br>  * for each rank in a channel, while coarse is common.<br>  */<br>-static void dqsset(u8 ch, u8 lane, const struct dll_setting *setting)<br>+void dqsset(u8 ch, u8 lane, const struct dll_setting *setting)<br> {<br>      int rank;<br> <br>@@ -319,7 +319,7 @@<br>     }<br> }<br> <br>-static void dqset(u8 ch, u8 lane, const struct dll_setting *setting)<br>+void dqset(u8 ch, u8 lane, const struct dll_setting *setting)<br> {<br>   int rank;<br>     MCHBAR32(0x400 * ch + 0x5fc) = (MCHBAR32(0x400 * ch + 0x5fc)<br>@@ -1687,9 +1687,11 @@<br> <br>       // XXX tRD<br> <br>-        // XXX Write training<br>-<br>-     // XXX Read training<br>+ if (!fast_boot) {<br>+            if (s->selected_timings.mem_clk > MEM_CLOCK_667MHz)<br>+                    do_write_training(s);<br>+                do_read_training(s);<br>+ }<br> <br>  // DRADRB<br>     dradrb_ddr2(s);<br>diff --git a/src/northbridge/intel/x4x/x4x.h b/src/northbridge/intel/x4x/x4x.h<br>index eaec7c6..cd803ea 100644<br>--- a/src/northbridge/intel/x4x/x4x.h<br>+++ b/src/northbridge/intel/x4x/x4x.h<br>@@ -358,6 +358,10 @@<br> void rcven(struct sysinfo *s);<br> u32 fsb2mhz(u32 speed);<br> u32 ddr2mhz(u32 speed);<br>+void dqsset(u8 ch, u8 lane, const struct dll_setting *setting);<br>+void dqset(u8 ch, u8 lane, const struct dll_setting *setting);<br>+int do_write_training(struct sysinfo *s);<br>+int do_read_training(struct sysinfo *s);<br> <br> extern const struct dll_setting default_ddr2_667_ctrl[7];<br> extern const struct dll_setting default_ddr2_800_ctrl[7];<br></pre><p>To view, visit <a href="https://review.coreboot.org/22329">change 22329</a>. To unsubscribe, visit <a href="https://review.coreboot.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://review.coreboot.org/22329"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: coreboot </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: newchange </div>
<div style="display:none"> Gerrit-Change-Id: Iacdc63b91b4705d1a80437314bfe55385ea5b6c1 </div>
<div style="display:none"> Gerrit-Change-Number: 22329 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Arthur Heymans <arthur@aheymans.xyz> </div>