{"id":2230919,"url":"http://patchwork.ozlabs.org/api/1.2/patches/2230919/?format=json","web_url":"http://patchwork.ozlabs.org/project/uboot/patch/d9038d69963976399d8e8e26dbf84b98ee1f82da.1777536451.git.simona.toaca@nxp.com/","project":{"id":18,"url":"http://patchwork.ozlabs.org/api/1.2/projects/18/?format=json","name":"U-Boot","link_name":"uboot","list_id":"u-boot.lists.denx.de","list_email":"u-boot@lists.denx.de","web_url":null,"scm_url":null,"webscm_url":null,"list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<d9038d69963976399d8e8e26dbf84b98ee1f82da.1777536451.git.simona.toaca@nxp.com>","list_archive_url":null,"date":"2026-04-30T08:33:30","name":"[v4,1/4] imx9: Add support for saving DDR training data to NVM","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"ac0404b82405bd6ab469cfdb53e00a6ad0514bd8","submitter":{"id":92829,"url":"http://patchwork.ozlabs.org/api/1.2/people/92829/?format=json","name":"Simona Toaca (OSS)","email":"simona.toaca@oss.nxp.com"},"delegate":{"id":151988,"url":"http://patchwork.ozlabs.org/api/1.2/users/151988/?format=json","username":"festevam","first_name":"Fabio","last_name":"Estevam","email":"festevam@gmail.com"},"mbox":"http://patchwork.ozlabs.org/project/uboot/patch/d9038d69963976399d8e8e26dbf84b98ee1f82da.1777536451.git.simona.toaca@nxp.com/mbox/","series":[{"id":502232,"url":"http://patchwork.ozlabs.org/api/1.2/series/502232/?format=json","web_url":"http://patchwork.ozlabs.org/project/uboot/list/?series=502232","date":"2026-04-30T08:33:29","name":"imx9{4,5,52}: Add Quickboot support","version":4,"mbox":"http://patchwork.ozlabs.org/series/502232/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2230919/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2230919/checks/","tags":{},"related":[],"headers":{"Return-Path":"<u-boot-bounces@lists.denx.de>","X-Original-To":"incoming@patchwork.ozlabs.org","Delivered-To":"patchwork-incoming@legolas.ozlabs.org","Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=NXP1.onmicrosoft.com header.i=@NXP1.onmicrosoft.com\n header.a=rsa-sha256 header.s=selector1-NXP1-onmicrosoft-com\n header.b=X4z0OQbn;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de\n (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de;\n envelope-from=u-boot-bounces@lists.denx.de; receiver=patchwork.ozlabs.org)","phobos.denx.de;\n dmarc=none (p=none dis=none) header.from=oss.nxp.com","phobos.denx.de;\n spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de","phobos.denx.de;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=NXP1.onmicrosoft.com header.i=@NXP1.onmicrosoft.com\n header.b=\"X4z0OQbn\";\n\tdkim-atps=neutral","phobos.denx.de;\n dmarc=none (p=none dis=none) header.from=oss.nxp.com","phobos.denx.de;\n spf=pass smtp.mailfrom=simona.toaca@oss.nxp.com","dkim=none (message not signed)\n header.d=none;dmarc=none action=none header.from=oss.nxp.com;"],"Received":["from phobos.denx.de (phobos.denx.de\n [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g5nHl2Sfxz1yGq\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 30 Apr 2026 18:23:30 +1000 (AEST)","from h2850616.stratoserver.net (localhost [IPv6:::1])\n\tby phobos.denx.de (Postfix) with ESMTP id C3D378466D;\n\tThu, 30 Apr 2026 10:23:20 +0200 (CEST)","by phobos.denx.de (Postfix, from userid 109)\n id 3D85C84653; Thu, 30 Apr 2026 10:23:19 +0200 (CEST)","from AS8PR04CU009.outbound.protection.outlook.com\n (mail-westeuropeazlp170110003.outbound.protection.outlook.com\n [IPv6:2a01:111:f403:c201::3])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits))\n (No client certificate requested)\n by phobos.denx.de (Postfix) with ESMTPS id E96F884661\n for <u-boot@lists.denx.de>; Thu, 30 Apr 2026 10:23:16 +0200 (CEST)","from VI0PR04MB11917.eurprd04.prod.outlook.com (2603:10a6:800:306::9)\n by DB8PR04MB7036.eurprd04.prod.outlook.com (2603:10a6:10:12f::21)\n with Microsoft SMTP Server (version=TLS1_2,\n cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9870.20; Thu, 30 Apr\n 2026 08:23:14 +0000","from VI0PR04MB11917.eurprd04.prod.outlook.com\n ([fe80::a506:3460:d2bc:26e7]) by VI0PR04MB11917.eurprd04.prod.outlook.com\n ([fe80::a506:3460:d2bc:26e7%4]) with mapi id 15.20.9870.016; Thu, 30 Apr 2026\n 08:23:13 +0000"],"X-Spam-Checker-Version":"SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de","X-Spam-Level":"","X-Spam-Status":"No, score=-0.9 required=5.0 tests=BAYES_00,DKIM_SIGNED,\n DKIM_VALID,FORGED_SPF_HELO,RCVD_IN_DNSWL_BLOCKED,SPF_HELO_PASS,\n T_SPF_PERMERROR autolearn=no autolearn_force=no version=3.4.2","ARC-Seal":"i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none;\n b=GqI0COIPtjw25c0YK80FcGlMk9iqSQpPKgjnwxbS1H4hiVi+AfRaj/scimUPr/z4DvfrXPhdKdWMguI36RikFcQdy+yMhULZFAbzdOtlQLgmI5Y/+hU6s4+EUMgbwHmcbyTIqkOYHxa9xy9p/TxaJtUuBVqco1hVDV/OjoRkgKBivYjnqxWs5sy6SdWzjKAo+vGlK+V/lyiO+TXTFjiPRmy67dm+Ic5hNzsbYP7IOOtXUJYCgsIgNrfQLw8/UNHucGuMn/6JfoTtSuNJctePpvbxbUQQHsNiGrN9KD47BufUFvc3SXNhxFBUda4MmrDtollFyScCN/MzwFJpzgb/QA==","ARC-Message-Signature":"i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com;\n s=arcselector10001;\n h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1;\n bh=qnY15AgMaQbhqI4Za7s7oA9yIfoYsBHL4PN0lP1niVQ=;\n b=Iz/K9BBmnx0PNp+005Z7UxgQSAK99LOHP+NMYRNrnfjNaCB396zx2jRb/RZjumPAc49Lu+pWng/Z0n4CfscqfrC+GnZTJUMBCxannI5sN/K5dF6rX1PcdV8EfSIJ+umpjIBMBAwTb0CkjPsLuuHQGfVwpcr44IwnJlT7OvUDjD046KQfGY1vRAuRmlevP+r8M0bBi7Fb6eAqhmTt0zwuLDE9AmovPYyx5PWTByIqx5UAcs5mcqkfZ25rYKyS/BnQJoglTkzj52RyUKD/KWN9TJBG8g+LMOXpUx0K2/CiGECIzTvjl5XXV5YW49wSL7LwrMEfdSmBECGuzZptBwpuGg==","ARC-Authentication-Results":"i=1; mx.microsoft.com 1; spf=pass\n smtp.mailfrom=oss.nxp.com; dmarc=pass action=none header.from=oss.nxp.com;\n dkim=pass header.d=oss.nxp.com; arc=none","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=NXP1.onmicrosoft.com;\n s=selector1-NXP1-onmicrosoft-com;\n h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck;\n bh=qnY15AgMaQbhqI4Za7s7oA9yIfoYsBHL4PN0lP1niVQ=;\n b=X4z0OQbnO33tLNGDD77XBFm7zRsr80EPHDNUf533L5wE76ukCxG0aofGh80DCjce4ZWWEY4OijpA4Yf5LpA2WbWguiCbHdKSGGR4DHOxRV40TAxpMnP8Ro1p643g6+XDnvDYeWDNcfmd+5xzlPowujhhgPe85okigOJOpON+TgHs2A+KbKAmC8KUzCvwaT7RmZr0M2iGyl+D1lkdKtOd6N+0iNw3cZL4VNW1xDji0ZiqG7mroAHALLaQc/xhxa9OLQwun6cdQ5u/yBgxIuAckmvFeWsDqhrOnyUvOA5gsk0sYg3CKc18ju7cZJ9yShxCjCD6c04iA5YDwFixoOrenw==","From":"\"Simona Toaca (OSS)\" <simona.toaca@oss.nxp.com>","To":"uboot-imx@nxp.com,\n\tu-boot@lists.denx.de","Cc":"Stefano Babic <sbabic@nabladev.com>, festevam@gmail.com, peng.fan@nxp.com,\n alice.guo@nxp.com, ye.li@nxp.com, simona.toaca@nxp.com,\n viorel.suman@nxp.com, fedor.ross@ifm.com, marex@nabladev.com,\n joao.goncalves@toradex.com, ravi@prevas.dk, ping.bai@nxp.com,\n ji.luo@nxp.com, qijian.guo@nxp.com","Subject":"[PATCH v4 1/4] imx9: Add support for saving DDR training data to NVM","Date":"Thu, 30 Apr 2026 11:33:30 +0300","Message-ID":"\n <d9038d69963976399d8e8e26dbf84b98ee1f82da.1777536451.git.simona.toaca@nxp.com>","X-Mailer":"git-send-email 2.43.0","In-Reply-To":"<cover.1777536451.git.simona.toaca@nxp.com>","References":"<cover.1777536451.git.simona.toaca@nxp.com>","Content-Transfer-Encoding":"8bit","Content-Type":"text/plain","X-ClientProxiedBy":"AM9P250CA0022.EURP250.PROD.OUTLOOK.COM\n (2603:10a6:20b:21c::27) To VI0PR04MB11917.eurprd04.prod.outlook.com\n (2603:10a6:800:306::9)","MIME-Version":"1.0","X-MS-Exchange-MessageSentRepresentingType":"1","X-MS-PublicTrafficType":"Email","X-MS-TrafficTypeDiagnostic":"VI0PR04MB11917:EE_|DB8PR04MB7036:EE_","X-MS-Office365-Filtering-Correlation-Id":"3fa4e2c3-e757-4795-a9f9-08dea691b89b","X-MS-Exchange-SharedMailbox-RoutingAgent-Processed":"True","X-LD-Processed":"686ea1d3-bc2b-4c6f-a92c-d99c5c301635,ExtAddr","X-MS-Exchange-SenderADCheck":"1","X-MS-Exchange-AntiSpam-Relay":"0","X-Microsoft-Antispam":"BCL:0;\n ARA:13230040|366016|1800799024|19092799006|376014|18002099003|56012099003|22082099003;","X-Microsoft-Antispam-Message-Info":"\n ihchGrCtiovIBI/LKTKvOUVMK3qvZCu8YWDsfFBVl1TTIA5ae7Lyz4XI80BrHie05+I6bPV+qWHDcwnXp0NEgJIbpyMe5s+3w3FtkWlZNiTtMuzRJybB6DVZyigb31pymTs831gw+UKSzm2rReMQjM9JOBTEUvaR2ws9AVCjaCpGn+DnBQfxiSA5emWKzdm07L/LGgk3Uq8+hXt01h0FlkswSxbNqw/5v/jg21YHl0SE63pLmCUX7VOk2v2kD4wwfimjOpuad15X5AzTD5mi5XcxpRVpca5zjyGkLk2x5sS6VEUwkuLf0+CEEbsTKW2699l+NYpLmz5t/MtAIhsj0EQMSUWX1ixwoMUiUChNnbBWMNcLqPi6wpZRKV76VVs1FOsch38s/oW2oz30g/YieqDT+K4N0pcV0exafHIijvIEAZEUfHrARzjXxVYGtqM4Eg70H5sAkwkQZiD6977kbsj9a2lzCF7WSb2mLgkcLan5uQnbfUkxaLwm58+LPvec52vxmxz4wiNgL8A2GRkwHyMpZ3NchDzlppea6mK5XdekhQysSsi4MULQV/jUPn17QL9UeMlCFDTbm/sbR5bkjUB3oB7PYKDj38ld0C3AmqUCZnFzMrX0rs0p4uuPeZYKp/c86H/fwpYF2b5MsxNbU9gogT7YqFB7Z+LiCRIr1svRH1ZBiwyaKKNosYkyFXCe3n4uLj+vNvoQC0+YML+NAClHTLSjiPqzhOVQ4IX6H4g=","X-Forefront-Antispam-Report":"CIP:255.255.255.255; CTRY:; LANG:en; SCL:1; SRV:;\n IPV:NLI; SFV:NSPM; H:VI0PR04MB11917.eurprd04.prod.outlook.com; PTR:;\n CAT:NONE;\n SFS:(13230040)(366016)(1800799024)(19092799006)(376014)(18002099003)(56012099003)(22082099003);\n DIR:OUT; SFP:1101;","X-MS-Exchange-AntiSpam-MessageData-ChunkCount":"1","X-MS-Exchange-AntiSpam-MessageData-0":"\n DZpY5rwCx9jxlzBt8ULfRDKRHP7ROmXYpiBr7KQZtFUpP9G3LkefFRme/k5IikLXweHPmNO37js5yk3Kv004AmF1srbllLi8ewmjspawykYRsFEB8EBiZhEcdF1c6lCz7a71CUflr0tVt+2XkJhK29dCbqaAMzlI7e65JyYERjhpY3OVmB5zjLuLIjAnWHrMqoeGOI2i6PqIuoWy7qCdIeGeJicophc0tJNLpGgqT8B/WwvuWbpJ3rfVFX72H3EYUnEik0jV7nk475tudNrkpS0sdMtBEAqZVJ2DehwtQXgHmw8pKB9MJt8+ExE7rr9EMil/RM5qhw03H8ayhA7trOWMwMZ3T5cnVVkxJMx1zUD3I0Is8TeLhuI8dY/kdr2Zisb+TMbEn1vPXdnUa+dEVGo/z+J7Vbfu/dftqicwQY8BkenwOaDtr8N4Xn6INy6VtUfrTfqKXTGPTNlQigfXNXOJQmCqNaKkytcnYarNMQqSzHaa3+HlKNtXvKPDXTVLwsnmNjPiGFjR2Zmpfnw5iJeYxLxItpGlZ9zs9mS14kkY3hzwmZcB2c+slAwy6JBmr6jMaKPUobYSCicLnyTJraqlF+svmX5SPKzIg3p0jfBsM/Rk5noK1TE64I2Kt89ff5Xe0pd84Y4N08lmLlGFLfgNGpDPdmCWJovW+kbGxnptU05m3T3LXdbhbE4Y2nu4XKgtaqpdohelv6lecQmSnNrcgVeSQw33RQQJdnNLctD9gBO3OGWGfUHnBOjj7QOa022rDfIzJ9vNky2q1pjaLunrVZ/rFUwIpFglP48RrEwGVJFFBpp6U+8mAWNyJyTXpcj80GA7OxGS+aHFCJak41S/T1Qgs2kihtgCp0DKankSMYXT0XOl8Nv0Yob3QqvR2VCtO/COHpFIL4Bl/wxDztxeXU+mgzeTmpm8OJSFL/3AuZUOTnlQX+Vv7KwRfrxxvuB3PDJxOCk5ycLaQ/tId996IR4L8c9wvLS654hr+Rm3PtDoL88ZHwVwrp2AOwhdFxnMApr3deO9Bh3Sy0NfBjbg6OPR4KxsiZ31Jl21BW23LWY6MCazFXTwMbkGkL1Z9+36c6kfqiUYBlUpyOC6nR4hNu4W6pn7QpkSRo1gcz7xCryVk7M7f9RsNRXQmnTPKwROCXcQYWXnaZ2/Ww7l5R8AsJ2Tqc1dO6Two80TCSlGCzs2p9X4Z+vPnzIFk5HEkus9OSauNqZFCrkvh61qlabOI1nkNa/V8gg+vjkPXlpWokjvQCeaNZHeNjiU50EgLjcDe4Nmad440TS5hHuKjaMfBpWbYJTPRys+mLC0lPIhsrGK7wMxw5fXQIBZ+ZsRCjfTezYOLZ2mtLYZ9tBtHcMyskPsT6h8oU7/HB87plwy0znJxQlGmSqSRF8MyW1yyyzMIa8wOJTJN4rSOuGVDmYNUWuvWfpJEs+4O8nNFqNbBGpOasu6xs82XRHuBRtYAlIbLYktu7YN5ozRwHjXWG1pm5u+C5brAB8OEPtcvH0q9OI+Q9OM2F7laolToIbhSkpBvjo1eg7F8zrveAP1DZfnzF7XKwnyUkKdvwaDxw/0qoYY4Llt7Z4G/QO+GD+S4pyAwgyD3xKGtHxsCWrVo2MdC6oCtGIQyXC+2Vx4OazUSDvx3S0KQ/Djq7BK23F9VzPvxrYAis5WSrF8D9R1zKYR0pJ7NlwNo82hRkv6xGY3wj1O7Sg6MsIcE7JrMG+0FAT6gE61Xq9iZoNygD0xUQ==","X-OriginatorOrg":"oss.nxp.com","X-MS-Exchange-CrossTenant-Network-Message-Id":"\n 3fa4e2c3-e757-4795-a9f9-08dea691b89b","X-MS-Exchange-CrossTenant-AuthSource":"VI0PR04MB11917.eurprd04.prod.outlook.com","X-MS-Exchange-CrossTenant-AuthAs":"Internal","X-MS-Exchange-CrossTenant-OriginalArrivalTime":"30 Apr 2026 08:23:13.3676 (UTC)","X-MS-Exchange-CrossTenant-FromEntityHeader":"Hosted","X-MS-Exchange-CrossTenant-Id":"686ea1d3-bc2b-4c6f-a92c-d99c5c301635","X-MS-Exchange-CrossTenant-MailboxType":"HOSTED","X-MS-Exchange-CrossTenant-UserPrincipalName":"\n Xr3m7M6pLcaMDby9PnMwIJlz4zgDyZoUCGwZ5rfE6gG/FMRfQJjfztf8fTHmZlKRGMrdQn8SeRWf6wf2MS2h5g==","X-MS-Exchange-Transport-CrossTenantHeadersStamped":"DB8PR04MB7036","X-BeenThere":"u-boot@lists.denx.de","X-Mailman-Version":"2.1.39","Precedence":"list","List-Id":"U-Boot discussion <u-boot.lists.denx.de>","List-Unsubscribe":"<https://lists.denx.de/options/u-boot>,\n <mailto:u-boot-request@lists.denx.de?subject=unsubscribe>","List-Archive":"<https://lists.denx.de/pipermail/u-boot/>","List-Post":"<mailto:u-boot@lists.denx.de>","List-Help":"<mailto:u-boot-request@lists.denx.de?subject=help>","List-Subscribe":"<https://lists.denx.de/listinfo/u-boot>,\n <mailto:u-boot-request@lists.denx.de?subject=subscribe>","Errors-To":"u-boot-bounces@lists.denx.de","Sender":"\"U-Boot\" <u-boot-bounces@lists.denx.de>","X-Virus-Scanned":"clamav-milter 0.103.8 at phobos.denx.de","X-Virus-Status":"Clean"},"content":"From: Simona Toaca <simona.toaca@nxp.com>\n\nDDR training data can be saved to NVM and be available\nto OEI at boot time, which will trigger QuickBoot flow.\n\nU-Boot only checks for data integrity (CRC32), while\nOEI is in charge of authentication when it tries to\nload the data from NVM.\n\nOn iMX95 A0/A1, 'authentication' is done via another\nCRC32. On the other SoCs, authentication is done by\nusing ELE to check the MAC stored in the ddrphy_qb_state\nstructure.\n\nSupported platforms: iMX94, iMX95, iMX952 (using OEI)\nSupported storage types: eMMC, SD, SPI flash.\n\nSigned-off-by: Viorel Suman <viorel.suman@nxp.com>\nSigned-off-by: Ye Li <ye.li@nxp.com>\nSigned-off-by: Simona Toaca <simona.toaca@nxp.com>\n---\n arch/arm/include/asm/arch-imx9/ddr.h |  48 +++-\n arch/arm/include/asm/mach-imx/qb.h   |  15 +\n arch/arm/mach-imx/Kconfig            |   9 +\n arch/arm/mach-imx/imx9/Makefile      |   6 +-\n arch/arm/mach-imx/imx9/qb.c          | 403 +++++++++++++++++++++++++++\n arch/arm/mach-imx/imx9/scmi/soc.c    |   7 +\n drivers/ddr/imx/imx9/Kconfig         |   7 +\n 7 files changed, 492 insertions(+), 3 deletions(-)\n create mode 100644 arch/arm/include/asm/mach-imx/qb.h\n create mode 100644 arch/arm/mach-imx/imx9/qb.c","diff":"diff --git a/arch/arm/include/asm/arch-imx9/ddr.h b/arch/arm/include/asm/arch-imx9/ddr.h\nindex a8e3f7354c7..bba12369f06 100644\n--- a/arch/arm/include/asm/arch-imx9/ddr.h\n+++ b/arch/arm/include/asm/arch-imx9/ddr.h\n@@ -1,6 +1,6 @@\n /* SPDX-License-Identifier: GPL-2.0+ */\n /*\n- * Copyright 2022 NXP\n+ * Copyright 2022-2026 NXP\n  */\n \n #ifndef __ASM_ARCH_IMX8M_DDR_H\n@@ -100,6 +100,52 @@ struct dram_timing_info {\n \n extern struct dram_timing_info dram_timing;\n \n+/* Quick Boot related */\n+#define DDRPHY_QB_CSR_SIZE\t5168\n+#define DDRPHY_QB_ACSM_SIZE\t(4 * 1024)\n+#define DDRPHY_QB_MSB_SIZE\t0x200\n+#define DDRPHY_QB_PSTATES\t0\n+#define DDRPHY_QB_PST_SIZE\t(DDRPHY_QB_PSTATES * 4 * 1024)\n+\n+/**\n+ * This structure needs to be aligned with the one in OEI.\n+ */\n+struct ddrphy_qb_state {\n+\tu32 crc;\t\t  /* Used for ensuring integrity in DRAM */\n+#define MAC_LENGTH\t\t8 /* 256 bits, 32-bit aligned */\n+\tu32 mac[MAC_LENGTH];\t  /* For 95A0/1 use mac[0] to keep CRC32 value */\n+\tu8 trained_vrefca_a0;\n+\tu8 trained_vrefca_a1;\n+\tu8 trained_vrefca_b0;\n+\tu8 trained_vrefca_b1;\n+\tu8 trained_vrefdq_a0;\n+\tu8 trained_vrefdq_a1;\n+\tu8 trained_vrefdq_b0;\n+\tu8 trained_vrefdq_b1;\n+\tu8 trained_vrefdqu_a0;\n+\tu8 trained_vrefdqu_a1;\n+\tu8 trained_vrefdqu_b0;\n+\tu8 trained_vrefdqu_b1;\n+\tu8 trained_dramdfe_a0;\n+\tu8 trained_dramdfe_a1;\n+\tu8 trained_dramdfe_b0;\n+\tu8 trained_dramdfe_b1;\n+\tu8 trained_dramdca_a0;\n+\tu8 trained_dramdca_a1;\n+\tu8 trained_dramdca_b0;\n+\tu8 trained_dramdca_b1;\n+\tu16 qb_pll_upll_prog0;\n+\tu16 qb_pll_upll_prog1;\n+\tu16 qb_pll_upll_prog2;\n+\tu16 qb_pll_upll_prog3;\n+\tu16 qb_pll_ctrl1;\n+\tu16 qb_pll_ctrl4;\n+\tu16 qb_pll_ctrl5;\n+\tu16 csr[DDRPHY_QB_CSR_SIZE];\n+\tu16 acsm[DDRPHY_QB_ACSM_SIZE];\n+\tu16 pst[DDRPHY_QB_PST_SIZE];\n+};\n+\n void ddr_load_train_firmware(enum fw_type type);\n int ddr_init(struct dram_timing_info *timing_info);\n int ddr_cfg_phy(struct dram_timing_info *timing_info);\ndiff --git a/arch/arm/include/asm/mach-imx/qb.h b/arch/arm/include/asm/mach-imx/qb.h\nnew file mode 100644\nindex 00000000000..a874c9c5e36\n--- /dev/null\n+++ b/arch/arm/include/asm/mach-imx/qb.h\n@@ -0,0 +1,15 @@\n+/* SPDX-License-Identifier: GPL-2.0+ */\n+/*\n+ * Copyright 2026 NXP\n+ */\n+\n+#ifndef __IMX_QB_H__\n+#define __IMX_QB_H__\n+\n+#include <stdbool.h>\n+\n+bool imx_qb_check(void);\n+int imx_qb(const char *ifname, const char *dev, bool save);\n+void spl_imx_qb_save(void);\n+\n+#endif\ndiff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig\nindex 259f4a4ce99..bb62a0cf2f6 100644\n--- a/arch/arm/mach-imx/Kconfig\n+++ b/arch/arm/mach-imx/Kconfig\n@@ -71,6 +71,15 @@ config CSF_SIZE\n \t  Define the maximum size for Command Sequence File (CSF) binary\n \t  this information is used to define the image boot data.\n \n+config IMX_QB\n+\tbool \"Support Quickboot flow for Synopsis DDR PHY on iMX platforms\"\n+\tdefault y\n+\tdepends on IMX94 || IMX95 || IMX952\n+\thelp\n+\t  Enable the logic for saving DDR training data from volatile\n+\t  memory to non-volatile storage. OEI uses the saved data to\n+\t  run Quickboot flow and skip re-training the DDR PHY.\n+\n config CMD_BMODE\n \tbool \"Support the 'bmode' command\"\n \tdefault y\ndiff --git a/arch/arm/mach-imx/imx9/Makefile b/arch/arm/mach-imx/imx9/Makefile\nindex 53cc97c6b47..80b697396ea 100644\n--- a/arch/arm/mach-imx/imx9/Makefile\n+++ b/arch/arm/mach-imx/imx9/Makefile\n@@ -1,6 +1,6 @@\n # SPDX-License-Identifier: GPL-2.0+\n #\n-# Copyright 2022 NXP\n+# Copyright 2022,2026 NXP\n \n obj-y += lowlevel_init.o\n \n@@ -12,4 +12,6 @@ endif\n \n ifneq ($(CONFIG_SPL_BUILD),y)\n obj-y += imx_bootaux.o\n-endif\n\\ No newline at end of file\n+endif\n+\n+obj-$(CONFIG_$(PHASE_)IMX_QB) += qb.o\ndiff --git a/arch/arm/mach-imx/imx9/qb.c b/arch/arm/mach-imx/imx9/qb.c\nnew file mode 100644\nindex 00000000000..1a0a12de3d4\n--- /dev/null\n+++ b/arch/arm/mach-imx/imx9/qb.c\n@@ -0,0 +1,403 @@\n+// SPDX-License-Identifier: GPL-2.0+\n+/**\n+ * Copyright 2024-2026 NXP\n+ */\n+#include <dm/device-internal.h>\n+#include <dm/uclass.h>\n+#include <errno.h>\n+#include <imx_container.h>\n+#include <linux/bitfield.h>\n+#include <mmc.h>\n+#include <spi_flash.h>\n+#include <spl.h>\n+#include <stdlib.h>\n+#include <u-boot/crc.h>\n+\n+#include <asm/arch/ddr.h>\n+#include <asm/mach-imx/boot_mode.h>\n+#include <asm/mach-imx/sys_proto.h>\n+\n+#define QB_STATE_LOAD_SIZE    SZ_64K\n+\n+#define BLK_DEV\t\t0\n+#define SPI_DEV\t\t1\n+\n+#define IMG_FLAGS_IMG_TYPE_MASK   0xF\n+#define IMG_FLAGS_IMG_TYPE(x)     FIELD_GET(IMG_FLAGS_IMG_TYPE_MASK, (x))\n+\n+#define IMG_TYPE_DDR_TDATA_DUMMY  0xD   /* dummy DDR training data image */\n+\n+static const struct {\n+\tconst char *ifname;\n+\tconst char *dev;\n+} imx_boot_devs[] = {\n+\t[BOOT_DEVICE_MMC1] = { \"mmc\", \"0\" },\n+\t[BOOT_DEVICE_MMC2] = { \"mmc\", \"1\" },\n+\t[BOOT_DEVICE_SPI]  = { \"spi\", \"\" },\n+};\n+\n+static int imx_qb_get_board_boot_device(void)\n+{\n+\tswitch (get_boot_device()) {\n+\tcase SD1_BOOT:\n+\tcase MMC1_BOOT:\n+\t\treturn BOOT_DEVICE_MMC1;\n+\tcase SD2_BOOT:\n+\tcase MMC2_BOOT:\n+\t\treturn BOOT_DEVICE_MMC2;\n+\tcase USB_BOOT:\n+\t\treturn BOOT_DEVICE_BOARD;\n+\tcase QSPI_BOOT:\n+\t\treturn BOOT_DEVICE_SPI;\n+\tdefault:\n+\t\treturn BOOT_DEVICE_NONE;\n+\t}\n+}\n+\n+static int imx_qb_get_boot_dev_str(const char **ifname, const char **dev)\n+{\n+\tint boot_dev;\n+\n+\tif (IS_ENABLED(CONFIG_XPL_BUILD))\n+\t\tboot_dev = spl_boot_device();\n+\telse\n+\t\tboot_dev = imx_qb_get_board_boot_device();\n+\n+\tif (boot_dev == BOOT_DEVICE_NONE || boot_dev == BOOT_DEVICE_BOARD)\n+\t\treturn -EINVAL;\n+\n+\t*ifname = imx_boot_devs[boot_dev].ifname;\n+\t*dev = imx_boot_devs[boot_dev].dev;\n+\n+\treturn 0;\n+}\n+\n+bool imx_qb_check(void)\n+{\n+\tstruct ddrphy_qb_state *qb_state;\n+\tu32 size, crc;\n+\n+\t/**\n+\t * Ensure CRC is not empty, the reason is that\n+\t * the data is invalidated after first save run\n+\t * or after it is overwritten.\n+\t */\n+\tqb_state = (struct ddrphy_qb_state *)CONFIG_QB_SAVED_STATE_BASE;\n+\tsize = sizeof(struct ddrphy_qb_state) - sizeof(qb_state->crc);\n+\tcrc = crc32(0, (u8 *)qb_state->mac, size);\n+\n+\tif (!qb_state->crc || crc != qb_state->crc)\n+\t\treturn false;\n+\n+\treturn true;\n+}\n+\n+static int imx_qb_get_blk_boot_part(const char * const ifname,\n+\t\t\t\t    const char * const dev,\n+\t\t\t\t    struct blk_desc **bdesc)\n+{\n+\tstruct udevice *udev;\n+\tstruct disk_partition info;\n+\tstruct mmc *mmc;\n+\tint part;\n+\tint ret;\n+\n+\tif (!IS_ENABLED(CONFIG_XPL_BUILD))\n+\t\treturn blk_get_device_part_str(ifname, dev, bdesc, &info, 1);\n+\n+\t/**\n+\t * SPL does not have access to part_get_info,\n+\t * so get the partition manually. Currently only\n+\t * supporting MMC devices.\n+\t */\n+\tret = blk_get_device_by_str(ifname, dev, bdesc);\n+\n+\tif (ret < 0)\n+\t\treturn -ENODEV;\n+\n+\tif ((*bdesc)->uclass_id != UCLASS_MMC)\n+\t\treturn -EOPNOTSUPP;\n+\n+\tudev = dev_get_parent((*bdesc)->bdev);\n+\tmmc = mmc_get_mmc_dev(udev);\n+\n+\tif (IS_SD(mmc) || mmc->part_config == MMCPART_NOAVAILABLE)\n+\t\treturn 0;\n+\n+\tpart = EXT_CSD_EXTRACT_BOOT_PART(mmc->part_config);\n+\n+\tif (part == EMMC_BOOT_PART_BOOT1 || part == EMMC_BOOT_PART_BOOT2)\n+\t\treturn part;\n+\n+\treturn 0;\n+}\n+\n+static ulong imx_qb_get_boot_device_offset(void *dev, int dev_type)\n+{\n+\tstruct blk_desc *bdesc;\n+\n+\tswitch (dev_type) {\n+\tcase BLK_DEV:\n+\t\tbdesc = dev;\n+\n+\t\t/* eMMC boot partition */\n+\t\tif (bdesc->hwpart)\n+\t\t\treturn CONTAINER_HDR_EMMC_OFFSET;\n+\n+\t\treturn CONTAINER_HDR_MMCSD_OFFSET;\n+\tcase SPI_DEV:\n+\t\treturn CONTAINER_HDR_QSPI_OFFSET;\n+\tdefault:\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+}\n+\n+static int imx_qb_parse_container(void *addr, u64 *qb_data_off)\n+{\n+\tstruct container_hdr *phdr;\n+\tstruct boot_img_t *img_entry;\n+\tu32 img_type, img_end;\n+\tint i;\n+\n+\tphdr = addr;\n+\tif (phdr->tag != 0x87 || (phdr->version != 0x0 && phdr->version != 0x2))\n+\t\treturn -EINVAL;\n+\n+\timg_entry = addr + sizeof(struct container_hdr);\n+\tfor (i = 0; i < phdr->num_images; i++) {\n+\t\timg_type = IMG_FLAGS_IMG_TYPE(img_entry->hab_flags);\n+\t\tif (img_type == IMG_TYPE_DDR_TDATA_DUMMY && img_entry->size == 0) {\n+\t\t\t/* Image entry pointing to DDR Training Data */\n+\t\t\t*qb_data_off = img_entry->offset;\n+\t\t\treturn 0;\n+\t\t}\n+\n+\t\timg_end = img_entry->offset + img_entry->size;\n+\t\tif (i + 1 < phdr->num_images) {\n+\t\t\timg_entry++;\n+\t\t\tif (img_end + QB_STATE_LOAD_SIZE == img_entry->offset) {\n+\t\t\t\t/* hole detected */\n+\t\t\t\t*qb_data_off = img_end;\n+\t\t\t\treturn 0;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\treturn -EINVAL;\n+}\n+\n+static int imx_qb_get_dev_qbdata_offset(void *dev, int dev_type, ulong offset,\n+\t\t\t\t\tu64 *qbdata_offset)\n+{\n+\tstruct blk_desc *bdesc;\n+\tu8 *buf;\n+\tulong count;\n+\tint ret;\n+\n+\tbuf = malloc(CONTAINER_HDR_ALIGNMENT);\n+\tif (!buf)\n+\t\treturn -ENOMEM;\n+\n+\tswitch (dev_type) {\n+\tcase BLK_DEV:\n+\t\tbdesc = dev;\n+\n+\t\tcount = blk_dread(bdesc,\n+\t\t\t\t  offset / bdesc->blksz,\n+\t\t\t\t  CONTAINER_HDR_ALIGNMENT / bdesc->blksz,\n+\t\t\t\t  buf);\n+\t\tif (count == 0) {\n+\t\t\tprintf(\"Read container image from MMC/SD failed\\n\");\n+\t\t\tret = -EIO;\n+\t\t\tgoto imx_qb_get_dev_qbdata_offset_exit;\n+\t\t}\n+\t\tbreak;\n+\tcase SPI_DEV:\n+\t\tif (!CONFIG_IS_ENABLED(SPI)) {\n+\t\t\tret = -EOPNOTSUPP;\n+\t\t\tgoto imx_qb_get_dev_qbdata_offset_exit;\n+\t\t}\n+\n+\t\tret = spi_flash_read_dm(dev, offset,\n+\t\t\t\t\tCONTAINER_HDR_ALIGNMENT, buf);\n+\t\tif (ret) {\n+\t\t\tprintf(\"Read container header from SPI failed\\n\");\n+\t\t\tret = -EIO;\n+\t\t\tgoto imx_qb_get_dev_qbdata_offset_exit;\n+\t\t}\n+\t\tbreak;\n+\tdefault:\n+\t\tprintf(\"Support for device %d not enabled\\n\", dev_type);\n+\t\tret = -EOPNOTSUPP;\n+\t\tgoto imx_qb_get_dev_qbdata_offset_exit;\n+\t}\n+\n+\tret = imx_qb_parse_container(buf, qbdata_offset);\n+\n+imx_qb_get_dev_qbdata_offset_exit:\n+\tfree(buf);\n+\n+\treturn ret;\n+}\n+\n+static int imx_qb_get_qbdata_offset(void *dev, int dev_type,\n+\t\t\t\t    u64 *qbdata_offset)\n+{\n+\tu64 cont_offset;\n+\tint ret, i;\n+\n+\tcont_offset = imx_qb_get_boot_device_offset(dev, dev_type);\n+\n+\tfor (i = 0; i < 3; i++) {\n+\t\tret = imx_qb_get_dev_qbdata_offset(dev, dev_type, cont_offset,\n+\t\t\t\t\t\t   qbdata_offset);\n+\t\tif (ret == 0) {\n+\t\t\t(*qbdata_offset) += cont_offset;\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tcont_offset += CONTAINER_HDR_ALIGNMENT;\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int imx_qb_blk(const char * const ifname,\n+\t\t      const char * const dev, bool save)\n+{\n+\tstruct blk_desc *bdesc;\n+\tu64 offset;\n+\tu64 load_size;\n+\tint part, orig_part;\n+\tint ret;\n+\n+\tpart = imx_qb_get_blk_boot_part(ifname, dev, &bdesc);\n+\n+\tif (part < 0) {\n+\t\tprintf(\"Failed to find %s %s\\n\", ifname, dev);\n+\t\treturn -ENODEV;\n+\t}\n+\n+\torig_part = bdesc->hwpart;\n+\n+\tret = blk_dselect_hwpart(bdesc, part);\n+\tif (ret && ret != -EMEDIUMTYPE) {\n+\t\tprintf(\"Failed to select hwpart, ret %d\\n\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\tret = imx_qb_get_qbdata_offset(bdesc, BLK_DEV, &offset);\n+\tif (ret) {\n+\t\tprintf(\"get_qbdata_offset failed, ret = %d\\n\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\toffset /= bdesc->blksz;\n+\tload_size = QB_STATE_LOAD_SIZE / bdesc->blksz;\n+\n+\tif (save) {\n+\t\t/* QB data is stored in DDR -> can use it as buf */\n+\t\tret = blk_dwrite(bdesc, offset, load_size,\n+\t\t\t\t (const void *)CONFIG_QB_SAVED_STATE_BASE);\n+\t} else {\n+\t\t/* erase */\n+\t\tret = blk_derase(bdesc, offset, load_size);\n+\t}\n+\n+\tif (!ret) {\n+\t\tprintf(\"Failed to write to block device\\n\");\n+\t\treturn -EIO;\n+\t}\n+\n+\t/* Return to original partition */\n+\tret = blk_dselect_hwpart(bdesc, orig_part);\n+\tif (ret && ret != -EMEDIUMTYPE) {\n+\t\tprintf(\"Failed to select hwpart, ret %d\\n\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int imx_qb_spi(bool save)\n+{\n+\tstruct udevice *flash;\n+\tu64 offset;\n+\tint ret;\n+\n+\tif (!CONFIG_IS_ENABLED(SPI)) {\n+\t\tprintf(\"SPI not enabled\\n\");\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tret = uclass_first_device_err(UCLASS_SPI_FLASH, &flash);\n+\tif (ret) {\n+\t\tprintf(\"SPI flash not found.\\n\");\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tret = imx_qb_get_qbdata_offset(flash, SPI_DEV, &offset);\n+\tif (ret) {\n+\t\tprintf(\"get_qbdata_offset failed, ret = %d\\n\", ret);\n+\t\treturn ret;\n+\t}\n+\n+\tret = spi_flash_erase_dm(flash, offset, QB_STATE_LOAD_SIZE);\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (!save)\n+\t\treturn 0;\n+\n+\t/* QB data is stored in DDR -> can use it as buf */\n+\tret = spi_flash_write_dm(flash, offset,\n+\t\t\t\t QB_STATE_LOAD_SIZE,\n+\t\t\t\t (const void *)CONFIG_QB_SAVED_STATE_BASE);\n+\n+\treturn ret;\n+}\n+\n+int imx_qb(const char *ifname, const char *dev, bool save)\n+{\n+\tint ret;\n+\n+\tret = 0;\n+\n+\t/* Try to use boot device  */\n+\tif (!strcmp(ifname, \"auto\"))\n+\t\tret = imx_qb_get_boot_dev_str(&ifname, &dev);\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (save && !imx_qb_check())\n+\t\treturn -EINVAL;\n+\n+\tif (!strcmp(ifname, \"spi\"))\n+\t\tret = imx_qb_spi(save);\n+\telse\n+\t\tret = imx_qb_blk(ifname, dev, save);\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (!save)\n+\t\treturn 0;\n+\n+\t/**\n+\t * invalidate qb_state mem so that at next boot\n+\t * the check function will fail and save won't happen\n+\t */\n+\tmemset((void *)CONFIG_QB_SAVED_STATE_BASE, 0,\n+\t       sizeof(struct ddrphy_qb_state));\n+\n+\treturn 0;\n+}\n+\n+void spl_imx_qb_save(void)\n+{\n+\t/* Save QB data on current boot device */\n+\tif (imx_qb(\"auto\", \"\", true))\n+\t\tprintf(\"QB save failed\\n\");\n+}\ndiff --git a/arch/arm/mach-imx/imx9/scmi/soc.c b/arch/arm/mach-imx/imx9/scmi/soc.c\nindex 47e8fc247df..ab0f7394c9e 100644\n--- a/arch/arm/mach-imx/imx9/scmi/soc.c\n+++ b/arch/arm/mach-imx/imx9/scmi/soc.c\n@@ -310,6 +310,13 @@ static struct mm_region imx9_mem_map[] = {\n \t\t.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |\n \t\t\t PTE_BLOCK_NON_SHARE |\n \t\t\t PTE_BLOCK_PXN | PTE_BLOCK_UXN\n+\t}, {\n+\t\t/* QB data */\n+\t\t.virt = CONFIG_QB_SAVED_STATE_BASE,\n+\t\t.phys = CONFIG_QB_SAVED_STATE_BASE,\n+\t\t.size = 0x200000UL,\t/* 2M */\n+\t\t.attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) |\n+\t\t\t PTE_BLOCK_OUTER_SHARE\n \t}, {\n \t\t/* empty entry to split table entry 5 if needed when TEEs are used */\n \t\t0,\ndiff --git a/drivers/ddr/imx/imx9/Kconfig b/drivers/ddr/imx/imx9/Kconfig\nindex b953bca4f06..7b3dbf53dff 100644\n--- a/drivers/ddr/imx/imx9/Kconfig\n+++ b/drivers/ddr/imx/imx9/Kconfig\n@@ -29,4 +29,11 @@ config SAVED_DRAM_TIMING_BASE\n \t  after DRAM is trained, need to save the dram related timming\n \t  info into memory for low power use.\n \n+config QB_SAVED_STATE_BASE\n+\thex \"Define the base address for saved QuickBoot state\"\n+\tdefault 0x8fe00000\n+\thelp\n+\t  Once DRAM is trained, the resulted training info is\n+\t  saved into memory in order to be reachable from U-Boot.\n+\n endmenu\n","prefixes":["v4","1/4"]}