ddr: altera: agilex5: LPDDRs in-line ECC support
In-line ECC support was added for LPDDR by reserving the last one-eighth
of the memory space for ECC data. Full memory initialization using the
BIST MEM INIT mailbox command, based on address and size, is required to
correctly generate ECC data and enable proper ECC logic verification.
Signed-off-by: Tingting Meng <tingting.meng@altera.com>
diff --git a/drivers/ddr/altera/iossm_mailbox.c b/drivers/ddr/altera/iossm_mailbox.c
index db9435d..2660285 100644
--- a/drivers/ddr/altera/iossm_mailbox.c
+++ b/drivers/ddr/altera/iossm_mailbox.c
@@ -10,6 +10,7 @@
#include <asm/arch/base_addr_soc64.h>
#include <asm/io.h>
#include <linux/bitfield.h>
+#include <linux/sizes.h>
#include "iossm_mailbox.h"
#define TIMEOUT_120000MS 120000
@@ -87,6 +88,7 @@
/* offset info of ECC_ENABLE_INTF */
#define INTF_ECC_ENABLE_TYPE_MASK GENMASK(1, 0)
+#define INTF_ECC_TYPE_MASK BIT(8)
/* cmd opcode BIST_MEM_INIT_START, BIST performed on full memory address range */
#define BIST_FULL_MEM BIT(6)
@@ -107,6 +109,10 @@
#define MAX_ECC_ERR_INFO_COUNT 16
+#define BIST_START_ADDR_SPACE_MASK GENMASK(5, 0)
+#define BIST_START_ADDR_LOW_MASK GENMASK(31, 0)
+#define BIST_START_ADDR_HIGH_MASK GENMASK(37, 32)
+
#define IO96B_MB_REQ_SETUP(v, w, x, y, z) \
usr_req.ip_type = v; \
usr_req.ip_id = w; \
@@ -512,7 +518,7 @@
{
int i, j, ret = 0;
u32 mem_width_info;
- u16 memory_size, total_memory_size = 0;
+ phys_size_t memory_size, total_memory_size = 0;
u32 mem_total_capacity_intf_offset[MAX_MEM_INTERFACE_SUPPORTED] = {
IOSSM_MEM_TOTAL_CAPACITY_INTF0_OFFSET,
@@ -526,8 +532,11 @@
mem_width_info = readl(io96b_ctrl->io96b[i].io96b_csr_addr +
mem_total_capacity_intf_offset[j]);
- memory_size = memory_size +
- FIELD_GET(INTF_CAPACITY_GBITS_MASK, mem_width_info);
+ io96b_ctrl->io96b[i].mb_ctrl.memory_size[j] =
+ FIELD_GET(INTF_CAPACITY_GBITS_MASK, mem_width_info) * SZ_1G / SZ_8;
+
+ if (io96b_ctrl->io96b[i].mb_ctrl.memory_size[j] != 0)
+ memory_size += io96b_ctrl->io96b[i].mb_ctrl.memory_size[j];
}
if (!memory_size) {
@@ -536,8 +545,6 @@
goto err;
}
- io96b_ctrl->io96b[i].size = memory_size;
-
total_memory_size = total_memory_size + memory_size;
}
@@ -556,7 +563,7 @@
{
int i, j, ret = 0;
u32 ecc_enable_intf;
- bool ecc_stat, ecc_stat_set = false;
+ bool ecc_status, ecc_status_set = false, inline_ecc = false;
u32 ecc_enable_intf_offset[MAX_MEM_INTERFACE_SUPPORTED] = {
IOSSM_ECC_ENABLE_INTF0_OFFSET,
@@ -565,6 +572,7 @@
/* Initialize ECC status */
io96b_ctrl->ecc_status = false;
+ io96b_ctrl->inline_ecc = false;
/* Get and ensure all memory interface(s) same ECC status */
for (i = 0; i < io96b_ctrl->num_instance; i++) {
@@ -572,15 +580,21 @@
ecc_enable_intf = readl(io96b_ctrl->io96b[i].io96b_csr_addr +
ecc_enable_intf_offset[j]);
- ecc_stat = (FIELD_GET(INTF_ECC_ENABLE_TYPE_MASK, ecc_enable_intf)
+ ecc_status = (FIELD_GET(INTF_ECC_ENABLE_TYPE_MASK, ecc_enable_intf)
== 0) ? false : true;
+ inline_ecc = FIELD_GET(INTF_ECC_TYPE_MASK, ecc_enable_intf);
- if (!ecc_stat_set) {
- io96b_ctrl->ecc_status = ecc_stat;
- ecc_stat_set = true;
+ if (!ecc_status_set) {
+ io96b_ctrl->ecc_status = ecc_status;
+
+ if (io96b_ctrl->ecc_status)
+ io96b_ctrl->inline_ecc = inline_ecc;
+
+ ecc_status_set = true;
}
- if (ecc_stat != io96b_ctrl->ecc_status) {
+ if (ecc_status != io96b_ctrl->ecc_status ||
+ (io96b_ctrl->ecc_status && inline_ecc != io96b_ctrl->inline_ecc)) {
printf("%s: Mismatch DDR ECC status on IO96B_%d\n", __func__, i);
ret = -EINVAL;
@@ -673,7 +687,7 @@
return ecc_error_flag;
}
-int bist_mem_init_start(struct io96b_info *io96b_ctrl)
+int out_of_band_bist_mem_init_start(struct io96b_info *io96b_ctrl)
{
struct io96b_mb_req usr_req;
struct io96b_mb_resp usr_resp;
@@ -746,3 +760,126 @@
err:
return ret;
}
+
+int bist_mem_init_by_addr(struct io96b_info *io96b_ctrl, int inst_id, int intf_id,
+ phys_addr_t base_addr, phys_size_t size)
+{
+ struct io96b_mb_req usr_req;
+ struct io96b_mb_resp usr_resp;
+ int n, ret = 0;
+ bool bist_start, bist_success;
+ u32 mem_exp, mem_init_status_intf, start;
+ phys_size_t chunk_size;
+
+ u32 mem_init_status_offset[MAX_MEM_INTERFACE_SUPPORTED] = {
+ IOSSM_MEM_INIT_STATUS_INTF0_OFFSET,
+ IOSSM_MEM_INIT_STATUS_INTF1_OFFSET
+ };
+
+ /* Check if size is a power of 2 */
+ if (size == 0 || (size & (size - 1)) != 0) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ mem_exp = 0;
+ chunk_size = size;
+
+ while (chunk_size >>= 1)
+ mem_exp++;
+
+ /* Start memory initialization BIST on the specified address range */
+ IO96B_MB_REQ_SETUP(io96b_ctrl->io96b[inst_id].mb_ctrl.ip_type[intf_id],
+ io96b_ctrl->io96b[inst_id].mb_ctrl.ip_id[intf_id],
+ CMD_TRIG_CONTROLLER_OP, BIST_MEM_INIT_START, 0);
+
+ /* CMD_PARAM_0 bit[5:0] = mem_exp */
+ /* CMD_PARAM_0 bit[6]: 0 - on the specified address range */
+ usr_req.cmd_param[0] = FIELD_PREP(BIST_START_ADDR_SPACE_MASK, mem_exp);
+ /* Extract address fields START_ADDR[31:0] */
+ usr_req.cmd_param[1] = FIELD_GET(BIST_START_ADDR_LOW_MASK, base_addr);
+ /* Extract address fields START_ADDR[37:32] */
+ usr_req.cmd_param[2] = FIELD_GET(BIST_START_ADDR_HIGH_MASK, base_addr);
+ /* Initialize memory to all zeros */
+ usr_req.cmd_param[3] = 0;
+
+ bist_start = false;
+ bist_success = false;
+
+ /* Send request to DDR controller */
+ debug("%s:Initializing memory: Addr=0x%llx, Size=2^%u\n", __func__,
+ base_addr, mem_exp);
+ ret = io96b_mb_req(io96b_ctrl->io96b[inst_id].io96b_csr_addr,
+ usr_req, 0, &usr_resp);
+ if (ret)
+ goto err;
+
+ bist_start = IOSSM_CMD_RESPONSE_DATA_SHORT(usr_resp.cmd_resp_status)
+ & BIT(0);
+
+ if (!bist_start) {
+ printf("%s: Failed to initialize memory on IO96B_%d\n", __func__,
+ inst_id);
+ printf("%s: BIST_MEM_INIT_START Error code 0x%lx\n", __func__,
+ IOSSM_STATUS_CMD_RESPONSE_ERROR(usr_resp.cmd_resp_status));
+
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* Polling for the initiated memory initialization BIST status */
+ start = get_timer(0);
+ while (!bist_success) {
+ udelay(1);
+
+ mem_init_status_intf = readl(io96b_ctrl->io96b[inst_id].io96b_csr_addr +
+ mem_init_status_offset[intf_id]);
+
+ bist_success = FIELD_GET(INTF_BIST_STATUS_MASK, mem_init_status_intf);
+
+ if (!bist_success && (get_timer(start) > TIMEOUT)) {
+ printf("%s: Timeout initialize memory on IO96B_%d\n",
+ __func__, inst_id);
+ printf("%s: BIST_MEM_INIT_STATUS Error code 0x%lx\n",
+ __func__,
+ IOSSM_STATUS_CMD_RESPONSE_ERROR(usr_resp.cmd_resp_status));
+
+ ret = -ETIMEDOUT;
+ goto err;
+ }
+ }
+
+ debug("%s:DDR memory initializationat 0x%llx completed.\n", __func__, base_addr);
+
+err:
+ return ret;
+}
+
+int inline_ecc_bist_mem_init(struct io96b_info *io96b_ctrl)
+{
+ int i, j, ret = 0;
+
+ /* Memory initialization BIST performed on all memory interfaces */
+ for (i = 0; i < io96b_ctrl->num_instance; i++) {
+ for (j = 0; j < io96b_ctrl->io96b[i].mb_ctrl.num_mem_interface; j++) {
+ ret = bist_mem_init_by_addr(io96b_ctrl, i, j, 0,
+ io96b_ctrl->io96b[i].mb_ctrl.memory_size[j]);
+ if (ret) {
+ printf("Error: Memory init failed at Instance %d, Interface %d\n",
+ i, j);
+ goto err;
+ }
+ }
+ }
+
+err:
+ return ret;
+}
+
+int bist_mem_init_start(struct io96b_info *io96b_ctrl)
+{
+ if (io96b_ctrl->inline_ecc)
+ return inline_ecc_bist_mem_init(io96b_ctrl);
+ else
+ return out_of_band_bist_mem_init_start(io96b_ctrl);
+}
diff --git a/drivers/ddr/altera/iossm_mailbox.h b/drivers/ddr/altera/iossm_mailbox.h
index 6f79478..02d1db2 100644
--- a/drivers/ddr/altera/iossm_mailbox.h
+++ b/drivers/ddr/altera/iossm_mailbox.h
@@ -40,11 +40,13 @@
* @num_mem_interface: Number of memory interfaces instantiated
* @ip_type: IP type implemented on the IO96B
* @ip_instance_id: IP identifier for every IP instance implemented on the IO96B
+ * @memory_size[2]: Memory size for every IP instance implemented on the IO96B
*/
struct io96b_mb_ctrl {
u32 num_mem_interface;
u32 ip_type[2];
u32 ip_id[2];
+ phys_size_t memory_size[2];
};
/* CMD_REQ Register Definition */
@@ -53,6 +55,9 @@
#define CMD_TYPE_MASK GENMASK(23, 16)
#define CMD_OPCODE_MASK GENMASK(15, 0)
+/* Computes the Inline ECC data region size */
+#define CALC_INLINE_ECC_HW_SIZE(size) (((size) * 7) / 8)
+
/*
* IOSSM mailbox request
* @ip_type: IP type for the specified memory interface
@@ -83,13 +88,11 @@
/*
* IO96B instance specific information
*
- * @size: Memory size
* @io96b_csr_addr: IO96B instance CSR address
* @cal_status: IO96B instance calibration status
* @mb_ctrl: IOSSM mailbox required information
*/
struct io96b_instance {
- u16 size;
phys_addr_t io96b_csr_addr;
bool cal_status;
struct io96b_mb_ctrl mb_ctrl;
@@ -102,6 +105,7 @@
* @overall_cal_status: Overall calibration status for all IO96B instance(s)
* @ddr_type: DDR memory type
* @ecc_status: ECC enable status (false = disabled, true = enabled)
+ * @inline_ecc: Inline ECC or Out of Band ECC (false = Out of Band ECC, true = Inline ECC)
* @overall_size: Total DDR memory size
* @io96b[]: IO96B instance specific information
* @ckgen_lock: IO96B GEN PLL lock (false = not locked, true = locked)
@@ -115,7 +119,8 @@
bool overall_cal_status;
const char *ddr_type;
bool ecc_status;
- u16 overall_size;
+ bool inline_ecc;
+ phys_size_t overall_size;
struct io96b_instance io96b[MAX_IO96B_SUPPORTED];
bool ckgen_lock;
u8 num_port;
diff --git a/drivers/ddr/altera/sdram_agilex5.c b/drivers/ddr/altera/sdram_agilex5.c
index 801a6bb..ee66c72 100644
--- a/drivers/ddr/altera/sdram_agilex5.c
+++ b/drivers/ddr/altera/sdram_agilex5.c
@@ -291,7 +291,14 @@
goto err;
}
- hw_size = (phys_size_t)io96b_ctrl->overall_size * SZ_1G / SZ_8;
+ ret = ecc_enable_status(io96b_ctrl);
+ if (ret) {
+ printf("DDR: Failed to get ECC enabled status\n");
+
+ goto err;
+ }
+
+ hw_size = io96b_ctrl->overall_size;
/* Get bank configuration from devicetree */
ret = fdtdec_decode_ram_size(gd->fdt_blob, NULL, 0, NULL,
@@ -303,6 +310,9 @@
goto err;
}
+ if (io96b_ctrl->inline_ecc)
+ hw_size = CALC_INLINE_ECC_HW_SIZE(hw_size);
+
if (gd->ram_size > hw_size) {
printf("DDR: Warning: DRAM size from device tree (%lld MiB) exceeds\n",
gd->ram_size >> 20);
@@ -355,13 +365,6 @@
printf("%s: %lld MiB\n", io96b_ctrl->ddr_type, gd->ram_size >> 20);
- ret = ecc_enable_status(io96b_ctrl);
- if (ret) {
- printf("DDR: Failed to get ECC enabled status\n");
-
- goto err;
- }
-
/* Is HPS cold or warm reset? If yes, Skip full memory initialization if ECC
* enabled to preserve memory content
*/
diff --git a/drivers/ddr/altera/sdram_soc64.c b/drivers/ddr/altera/sdram_soc64.c
index 75c6de1..27fbe80 100644
--- a/drivers/ddr/altera/sdram_soc64.c
+++ b/drivers/ddr/altera/sdram_soc64.c
@@ -185,35 +185,51 @@
void sdram_size_check(struct bd_info *bd)
{
phys_size_t total_ram_check = 0;
- phys_size_t ram_check = 0;
- phys_addr_t start = 0;
- phys_size_t size, remaining_size;
int bank;
/* Sanity check ensure correct SDRAM size specified */
debug("DDR: Running SDRAM size sanity check\n");
for (bank = 0; bank < CONFIG_NR_DRAM_BANKS; bank++) {
+ phys_size_t ram_check = 0;
+ phys_addr_t start = 0;
+ phys_size_t remaining_size;
+
start = bd->bi_dram[bank].start;
remaining_size = bd->bi_dram[bank].size;
+ debug("Checking bank %d: start=0x%llx, size=0x%llx\n",
+ bank, start, remaining_size);
+
while (ram_check < bd->bi_dram[bank].size) {
- size = min((phys_addr_t)SZ_1G,
- (phys_addr_t)remaining_size);
+ phys_size_t size, test_size, detected_size;
+
+ size = min((phys_addr_t)SZ_1G, (phys_addr_t)remaining_size);
+
+ if (size < SZ_8) {
+ puts("Invalid size: Memory size required to be multiple\n");
+ puts("of 64-Bit word!\n");
+ hang();
+ }
+
+ /* Adjust size to the nearest power of two to support get_ram_size() */
+ test_size = SZ_8;
+
+ while (test_size * 2 <= size)
+ test_size *= 2;
- /*
- * Ensure the size is power of two, this is requirement
- * to run get_ram_size() / memory test
- */
- if (size != 0 && ((size & (size - 1)) == 0)) {
- ram_check += get_ram_size((void *)
- (start + ram_check), size);
- remaining_size = bd->bi_dram[bank].size -
- ram_check;
- } else {
- puts("DDR: Memory test requires SDRAM size ");
- puts("in power of two!\n");
+ debug("Testing memory at 0x%llx with size 0x%llx\n",
+ start + ram_check, test_size);
+ detected_size = get_ram_size((void *)(start + ram_check), test_size);
+
+ if (detected_size != test_size) {
+ debug("Detected size 0x%llx doesn’t match the test size 0x%llx!\n",
+ detected_size, test_size);
+ puts("Memory testing failed!\n");
hang();
}
+
+ ram_check += detected_size;
+ remaining_size = bd->bi_dram[bank].size - ram_check;
}
total_ram_check += ram_check;