{"id":2218990,"url":"http://patchwork.ozlabs.org/api/patches/2218990/?format=json","web_url":"http://patchwork.ozlabs.org/project/uboot/patch/7edcce04b39aabc1f58e09814e7fadff226e2bb8.1775121078.git.simona.toaca@nxp.com/","project":{"id":18,"url":"http://patchwork.ozlabs.org/api/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":"<7edcce04b39aabc1f58e09814e7fadff226e2bb8.1775121078.git.simona.toaca@nxp.com>","list_archive_url":null,"date":"2026-04-02T09:40:43","name":"[v3,1/6] imx9: Add support for saving DDR training data to NVM","commit_ref":null,"pull_url":null,"state":"changes-requested","archived":false,"hash":"72bea5e4fefde1f354c4c7cb6c54cd0afdf71fe6","submitter":{"id":92829,"url":"http://patchwork.ozlabs.org/api/people/92829/?format=json","name":"Simona Toaca","email":"simona.toaca@oss.nxp.com"},"delegate":{"id":151988,"url":"http://patchwork.ozlabs.org/api/users/151988/?format=json","username":"festevam","first_name":"Fabio","last_name":"Estevam","email":"festevam@gmail.com"},"mbox":"http://patchwork.ozlabs.org/project/uboot/patch/7edcce04b39aabc1f58e09814e7fadff226e2bb8.1775121078.git.simona.toaca@nxp.com/mbox/","series":[{"id":498456,"url":"http://patchwork.ozlabs.org/api/series/498456/?format=json","web_url":"http://patchwork.ozlabs.org/project/uboot/list/?series=498456","date":"2026-04-02T09:40:42","name":"imx9{4,5,52}: Add Quickboot support","version":3,"mbox":"http://patchwork.ozlabs.org/series/498456/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2218990/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2218990/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=heFut8uS;\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=\"heFut8uS\";\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 4fmc5265frz1yCs\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 02 Apr 2026 20:29:42 +1100 (AEDT)","from h2850616.stratoserver.net (localhost [IPv6:::1])\n\tby phobos.denx.de (Postfix) with ESMTP id 216B784101;\n\tThu,  2 Apr 2026 11:29:37 +0200 (CEST)","by phobos.denx.de (Postfix, from userid 109)\n id 45BFC8416F; Thu,  2 Apr 2026 11:29:36 +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 1ADE584105\n for <u-boot@lists.denx.de>; Thu,  2 Apr 2026 11:29:31 +0200 (CEST)","from VI0PR04MB11917.eurprd04.prod.outlook.com (2603:10a6:800:306::9)\n by AM7PR04MB7095.eurprd04.prod.outlook.com (2603:10a6:20b:11c::21)\n with Microsoft SMTP Server (version=TLS1_2,\n cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9745.28; Thu, 2 Apr\n 2026 09:29:29 +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.9769.016; Thu, 2 Apr 2026\n 09:29:28 +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=FVfBe5QTSVY4QcX2zI1r7GyARd3c8jOFh1d04PrSgv1UE/VxdZJUP9lodZ5KASztNGg8w2j0+WKUgBVb9iV+HfnjWINv/jcRG78JO+QoCMkcrGXFpJK6SNaUXXz6OWqgLhmbuP4JU302T4YjJ0GmeZU2dDh3kI1c5p0QVlJR7aLYS/KiIUrGNM0F2WXfMGSBdyyzQ2iWuD6IqTrm3ETvr3mVwGXfOqwJT7qYjfmKdS9YFb/JkxzNLEmdqjW0+JyT9+cdDgvW0W/El28+O0ZAbDrC8DuKQeJ8kdEXGX0Kn758k2l34cF+y2Zewd/6yStqshnjO0bDsgkrfrgxqnu6BA==","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=9i87pC02SZqITDDg6vBNSC75GOF//gNORXhM/kl/3AI=;\n b=Vnj62THmuRqMECjUAYZWHq+xTmL5ajecCMmVgnwuMIqAiiFDTfGgxutA+gl2rMMqkWVX42v+wAVPxObgpMFoOpRoxhO6yhZNfK53jTvg2+qHYhYQi6e3b4V8kedYOk02rtppVSkOSv73VliVjBJ9dWBAD0i+5cIm6qMDxZWjQY4+xlCEW0XKoGMRN8TqR1cexgSAM/1bqz49cEksjbbPpxFFauUrmDM7WpM5/0T5zpbagV7PV+5tPDnTgTL93j6sJ7GKtgAY9zvpybvyz8Ml5l+NqLejuMsdpWxZSzLjQeYzejA12HgIRCFcf3JEDnWPRZvFNBc+ZUwukSKsvmltjg==","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=9i87pC02SZqITDDg6vBNSC75GOF//gNORXhM/kl/3AI=;\n b=heFut8uSIBM2r8hdm3oa36twAzQpVszduNq3l/eyR0tPpBt4wD8ui+lc0rD9gwLFVtzCRn4ihKs/1DdDDH49eR4U+AFioe/sXYAPBR1BYhoq43FbBR3cLtnZB4S/jBfS79KW5D6jEtfDVRtSjQafZfS78tynuIUfYNN33w6Iip5oe5/z67VG1IfNK7hseTUzMIZ5P3Al0BmjVHJynrtFBKE8L7pB/RIh0naOMcDH8e6SqtfxONi6MW4rATf28pVFVOMCPa1zF4LZUx9gPJENb0VSnXAZT5AKwihlWG266p+AdfbbaYMMkWalBXYcp6wfP70f0x7Y3mVl75ZluniZVg==","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, viorel.suman@nxp.com, simona.toaca@nxp.com,\n ye.li@nxp.com, ping.bai@nxp.com, marex@nabladev.com,\n sebastien.szymanski@armadeus.com, ravi@prevas.dk,\n joao.goncalves@toradex.com, ji.luo@nxp.com, semen.protsenko@linaro.org,\n tharvey@gateworks.com, qijian.guo@nxp.com","Subject":"[PATCH v3 1/6] imx9: Add support for saving DDR training data to NVM","Date":"Thu,  2 Apr 2026 12:40:43 +0300","Message-ID":"\n <7edcce04b39aabc1f58e09814e7fadff226e2bb8.1775121078.git.simona.toaca@nxp.com>","X-Mailer":"git-send-email 2.43.0","In-Reply-To":"<cover.1775121078.git.simona.toaca@nxp.com>","References":"<cover.1775121078.git.simona.toaca@nxp.com>","Content-Transfer-Encoding":"8bit","Content-Type":"text/plain","X-ClientProxiedBy":"FR4P281CA0122.DEUP281.PROD.OUTLOOK.COM\n (2603:10a6:d10:b9::6) 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_|AM7PR04MB7095:EE_","X-MS-Office365-Filtering-Correlation-Id":"1c71b885-b63d-4dbc-2732-08de909a5692","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|19092799006|376014|1800799024|18002099003|22082099003|56012099003;","X-Microsoft-Antispam-Message-Info":"\n ygE95mLB/fLNhRmnlj5ob6UxgUUCtvLYtXLkWG7qA+o8pOtETOl+DVvLUoTOKgFTXK4JNzeWg9VBd/l7l4CunzADC8+zu1oKYwRBZ9MMeN0yC9satj0DvdTrSHg7AN3bOt2EjIKk9+v8myKGJN98mBZ6O0Zz4q/ZQWGf/cG1mqf8tvjBdHzSOjJdUtQ5QVJw5l/9PT9wMhXsXd0pELSOlWP0mCRd8+V5T4uk4sR8VYMEsQeYOpgE0X6MILM3hu/tn/oVpodmCmappkQEdzhkWWbjoOLpKDF39ozgRudAx/Ef8CnWO3T4GtnT72501q8UxGGQ8qllSfgeKZA37elunEXxBGt1j/YvRm94sO7jgXvGqSxMWHzbtbeImlH42QJ08FjcU7chJCov/oQzzdA43+0CLg3sq7KlgF5Wo6WcVwBmBmPgTBflLZe1C+1EJJc3Aq+jGHMJ9s5yuOiLcYM65Ok9rBQwrHjvRkTomtqJRrPpELATi8y6HP28AAWon7CX23+g939JSfi186+Ga8mEv7wjw00+dP9QPd/jtUcmwiv3UepOX+ci0sGQpmBpXcWyea0oBUATeuu8uXHD55LijwlCnaxfJnES2jRyPddS2kKDAYKcDtgLEar7X30SSo4cALxKbwwhXMmh32VTfaXJtx3elvQ13yUhtBIcfhzs+Gdmpxgvs93fkW8jo48D5mMpNCyUUB6lajRppNUFQiSDUcq/dWrnJVrzTHu03uzAy14=","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)(19092799006)(376014)(1800799024)(18002099003)(22082099003)(56012099003);\n DIR:OUT; SFP:1101;","X-MS-Exchange-AntiSpam-MessageData-ChunkCount":"1","X-MS-Exchange-AntiSpam-MessageData-0":"\n w94le6s5+0M24LFTBtYp4VpRzyaNEGoZJV27TGpuDX8EOZgRaUudK05/mLtlZqpx71umk5fLUGBisTlvL0vJgnh6rIidv5t9LqscoIGe9vlWSlmYXoykD+CHFgVMHNAVYKUDrke98BUw6CX+D+fjBD7P/8hjnGOpDQjjlVSFajW95I9NgoESvrJGb8e4cei5Yx03ZnvUq8je3c/haQdD1qoiBvTKrzBLnCQxAqIwKSYFr5yFzBzusDeMQcaonGgHPoZGO3W9bmQhYNTj2NmkQZ4wzln+2Z1gfFRuzIvdnlCmR4NcsjQico4XMv7xthr/d0B1txc/mTVEamCogWWMinlep9VJe1I8l+9ubHuDhh9UanovkAM3Hp3VPDxiC7kLfepG/ir2kPOS04mNPckjqW/5+pJsMNCaKGyIRLr0IUtZP3hCVdDxClYvYJ/2Ckw4z4OeFnsqT5JNqdRmqmXDPmzsyEugJgvQ1p9CK7n/b1uRxxxihsSmeAH4/vPTqLSAl3/oRSsAlHsqkHOYBrJ6WV+f+Ono3/tvH0n6AFSXDoZSWmWTmqJx6r19u4v7+UBqiB0WW4jCQ2oKB7NFSXTfOCAbYfxtuXzjJmW0dM7Gb2P3EIAVjQgZ1G7jUujSz2uScWzxuQU8XhHnE1iaDxE0sPr7LeuxnW3hEaWkZLcoBR1CejUPZVMCy9Z31Lxx91i8MVHGzkAgsYKEeY5r3JaehwZWyQA2oTprf5LjU6libD/PxA0mUiFQThC2hvccE+N8iWY67AhvlVCMDLPvzpGFEGnGtqhszMNMev1uexSeWpKJ71O+EypjqaktSs65E9cR2kwgfBbxlAr4FNxUnGs5blhYQfiEywBWgifVj75xZC6/JV4O2rTULiUomeieO/Ec6DcjWHeoX5fuyOGV+PKGyH3YnGdNaV9y02I8O4ljIaJ9ENvDKmyPsKyhG5jlsihSo0cdv7Fxkd5lRIzRt904vT/e0Pfd+nckv5L7AlBVqApEy4GzWxP+tH0bEZEnn9CW8S3VFtu2bIEP1YhQvdmjQGKsCyuOUSMZlAleuNUr7H438gJgAOflYM9p0fsA4YAGT92NSgQA6ld2sjKOC0TSUfPoWtrxs1BHAAF3Pue7Hs0C0P8c2x5MdtBR6uqg08toEEvwK2jI3ofDNYARnJMqGwVlm6oYVxMxQ351DLlHwYE/cwpL/xsm5WSobfCXTGqZ5D2O37297mI3dL1Y5fPyKBWeeZ8OBy3MSrVb2IauxVLm2Rhfy2fB2ElYEhXFFF+awGDvzZBBfHN6X9RB9+T2hfLwKT4Y59YVRN8kK2GV+aGdDWRA7lI1/T2kPq4pVv1BJ/9rpKnPkiMeRFgZWxBXVh0I2i0en9KfVBYWpLikDw86ZNz9R/CQ5tAtsPKl6+tgja/Hs14LlrGbLcFHNHi2ks1dMYNuviV12wX7TwYg9oRvqfLmFi3a84ZQLAsJmdSCt48CsYyXOEh9s2HYV3J3TAkbqj+wFyH2DFSbEsOdCEwsEsdh2bDEqalOm2Cfa37dGot58Kvky69lRnRzlbjG80xPXtuoMkurUvq8WwGxTA42shlCvMtmLcf7AvJiCs1GxVqmaUE6S/WycZkk7FPjgqSur7yQkFgEcjQo6nXyKCaJFAC+JTU2hfXwHhLSvQZna7lBrvCJFGwcNVdYK5eFNTOYPiPCQfLqQDRbqfVWtAWyXRJnwYOMnLMEE0Q/i3PhqbiBaRCaX0TfzPkhXgDewg==","X-OriginatorOrg":"oss.nxp.com","X-MS-Exchange-CrossTenant-Network-Message-Id":"\n 1c71b885-b63d-4dbc-2732-08de909a5692","X-MS-Exchange-CrossTenant-AuthSource":"VI0PR04MB11917.eurprd04.prod.outlook.com","X-MS-Exchange-CrossTenant-AuthAs":"Internal","X-MS-Exchange-CrossTenant-OriginalArrivalTime":"02 Apr 2026 09:29:28.8564 (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 lQkQ++/i/GrN0MzrdlaHk0CwE0QLyJaS5eOJBwf99UriAh93/9NhCQLnAln3YmhRQOZ7BZwYJo7PWUmmCGYBpQ==","X-MS-Exchange-Transport-CrossTenantHeadersStamped":"AM7PR04MB7095","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 boards, 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/imx9/Makefile      |   6 +-\n arch/arm/mach-imx/imx9/qb.c          | 379 +++++++++++++++++++++++++++\n arch/arm/mach-imx/imx9/scmi/soc.c    |   7 +\n drivers/ddr/imx/imx9/Kconfig         |   7 +\n 6 files changed, 459 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..290e3e4e06e 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              8 /* 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..aeec26718cb\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 qb_check(void);\n+int qb(int qb_dev, bool save);\n+void spl_qb_save(void);\n+\n+#endif\ndiff --git a/arch/arm/mach-imx/imx9/Makefile b/arch/arm/mach-imx/imx9/Makefile\nindex 53cc97c6b47..d2e44f0d5f9 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-y += 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..220545192d4\n--- /dev/null\n+++ b/arch/arm/mach-imx/imx9/qb.c\n@@ -0,0 +1,379 @@\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 MMC_DEV\t\t0\n+#define QSPI_DEV\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+bool 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 unsigned long qb_get_boot_device_offset(void *dev, int dev_type)\n+{\n+\tunsigned long offset = 0;\n+\tstruct mmc *mmc;\n+\n+\tswitch (dev_type) {\n+\tcase MMC_DEV:\n+\t\tmmc = dev;\n+\n+\t\tif (IS_SD(mmc) || mmc->part_config == MMCPART_NOAVAILABLE) {\n+\t\t\toffset = CONTAINER_HDR_MMCSD_OFFSET;\n+\t\t} else {\n+\t\t\tu8 part = EXT_CSD_EXTRACT_BOOT_PART(mmc->part_config);\n+\n+\t\t\tif (part == EMMC_BOOT_PART_BOOT1 || part == EMMC_BOOT_PART_BOOT2)\n+\t\t\t\toffset = CONTAINER_HDR_EMMC_OFFSET;\n+\t\t\telse\n+\t\t\t\toffset = CONTAINER_HDR_MMCSD_OFFSET;\n+\t\t}\n+\t\tbreak;\n+\tcase QSPI_DEV:\n+\t\toffset = CONTAINER_HDR_QSPI_OFFSET;\n+\t\tbreak;\n+\tdefault:\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\treturn offset;\n+}\n+\n+static int qb_parse_container(void *addr, u32 *qb_data_off)\n+{\n+\tstruct container_hdr *phdr;\n+\tstruct boot_img_t *img_entry;\n+\tint i = 0;\n+\tu32 img_type, img_end;\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 get_dev_qbdata_offset(void *dev, int dev_type, unsigned long offset,\n+\t\t\t\t u32 *qbdata_offset)\n+{\n+\tint ret = -EOPNOTSUPP;\n+\tu16 ctnr_hdr_align = CONTAINER_HDR_ALIGNMENT;\n+\tvoid *buf = kmalloc(ctnr_hdr_align, GFP_KERNEL);\n+\n+\tif (!buf) {\n+\t\tprintf(\"kmalloc buffer failed\\n\");\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tswitch (dev_type) {\n+\tcase MMC_DEV:\n+\t\tunsigned long count = 0;\n+\t\tstruct mmc *mmc = dev;\n+\n+\t\tif (!(CONFIG_IS_ENABLED(DM_MMC) && CONFIG_IS_ENABLED(MMC_WRITE)))\n+\t\t\tgoto get_dev_qbdata_offset_exit;\n+\n+\t\tcount = blk_dread(mmc_get_blk_desc(mmc),\n+\t\t\t\t  offset / mmc->read_bl_len,\n+\t\t\t\t  ctnr_hdr_align / mmc->read_bl_len,\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 get_dev_qbdata_offset_exit;\n+\t\t}\n+\t\tbreak;\n+\tcase QSPI_DEV:\n+\t\tif (!CONFIG_IS_ENABLED(SPI))\n+\t\t\tgoto get_dev_qbdata_offset_exit;\n+\n+\t\tret = spi_flash_read(dev, offset,\n+\t\t\t\t     ctnr_hdr_align, buf);\n+\t\tif (ret) {\n+\t\t\tprintf(\"Read container header from QSPI failed\\n\");\n+\t\t\tret = -EIO;\n+\t\t\tgoto 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\tgoto get_dev_qbdata_offset_exit;\n+\t}\n+\n+\tret = qb_parse_container(buf, qbdata_offset);\n+\n+get_dev_qbdata_offset_exit:\n+\tfree(buf);\n+\n+\treturn ret;\n+}\n+\n+static int get_qbdata_offset(void *dev, int dev_type, u32 *qbdata_offset)\n+{\n+\tu32 offset = qb_get_boot_device_offset(dev, dev_type);\n+\tu16 ctnr_hdr_align = CONTAINER_HDR_ALIGNMENT;\n+\tu32 cont_offset;\n+\tint ret, i;\n+\n+\tfor (i = 0; i < 3; i++) {\n+\t\tcont_offset = offset + i * ctnr_hdr_align;\n+\t\tret = get_dev_qbdata_offset(dev, dev_type, cont_offset, qbdata_offset);\n+\t\tif (ret == 0) {\n+\t\t\t(*qbdata_offset) += cont_offset;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\treturn ret;\n+}\n+\n+static int qb_mmc_get_device_index(u32 dev)\n+{\n+\tswitch (dev) {\n+\tcase BOOT_DEVICE_MMC1:\n+\t\treturn 0;\n+\tcase BOOT_DEVICE_MMC2:\n+\tcase BOOT_DEVICE_MMC2_2:\n+\t\treturn 1;\n+\t}\n+\n+\treturn -ENODEV;\n+}\n+\n+static int qb_mmc_find_device(struct mmc **mmcp, int mmc_dev)\n+{\n+\tint ret;\n+\n+\tret = mmc_init_device(mmc_dev);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t*mmcp = find_mmc_device(mmc_dev);\n+\n+\treturn *mmcp ? 0 : -ENODEV;\n+}\n+\n+static int qb_mmc(int dev, bool save)\n+{\n+\tstruct mmc *mmc;\n+\tint ret = 0, mmc_dev;\n+\tbool has_hw_part;\n+\tu8 orig_part, part;\n+\tu32 offset;\n+\tvoid *buf;\n+\n+\tif (!(CONFIG_IS_ENABLED(DM_MMC) && CONFIG_IS_ENABLED(MMC_WRITE))) {\n+\t\tprintf(\"Please enable MMC and MMC_WRITE.\\n\");\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tmmc_dev = qb_mmc_get_device_index(dev);\n+\tif (mmc_dev < 0)\n+\t\treturn mmc_dev;\n+\n+\tret = qb_mmc_find_device(&mmc, mmc_dev);\n+\tif (ret) {\n+\t\tprintf(\"MMC %d not found.\\n\", mmc_dev);\n+\t\treturn ret;\n+\t}\n+\n+\tret = mmc_init(mmc);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\thas_hw_part = !IS_SD(mmc) && mmc->part_config != MMCPART_NOAVAILABLE;\n+\n+\tif (has_hw_part) {\n+\t\torig_part = mmc_get_blk_desc(mmc)->hwpart;\n+\t\tpart = EXT_CSD_EXTRACT_BOOT_PART(mmc->part_config);\n+\n+\t\t/* Select the partition */\n+\t\tret = mmc_switch_part(mmc, part);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\tret = get_qbdata_offset(mmc, MMC_DEV, &offset);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (save) {\n+\t\t/* QB data is stored in DDR -> can use it as buf */\n+\t\tbuf = (void *)CONFIG_QB_SAVED_STATE_BASE;\n+\t\tret = blk_dwrite(mmc_get_blk_desc(mmc),\n+\t\t\t\t offset / mmc->write_bl_len,\n+\t\t\t\t QB_STATE_LOAD_SIZE / mmc->write_bl_len,\n+\t\t\t\t (const void *)buf);\n+\t} else {\n+\t\t/* erase */\n+\t\tret = blk_derase(mmc_get_blk_desc(mmc),\n+\t\t\t\t offset / mmc->write_bl_len,\n+\t\t\t\t QB_STATE_LOAD_SIZE / mmc->write_bl_len);\n+\t}\n+\n+\tif (!ret)\n+\t\treturn -EIO;\n+\n+\tif (!has_hw_part)\n+\t\treturn 0;\n+\n+\t/* Return to original partition */\n+\treturn mmc_switch_part(mmc, orig_part);\n+}\n+\n+static int qb_spi_find_device(struct spi_flash **dev)\n+{\n+\tint ret;\n+\tstruct udevice *udev;\n+\n+\tret = uclass_first_device_err(UCLASS_SPI_FLASH, &udev);\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t*dev = dev_get_uclass_priv(udev);\n+\n+\treturn *dev ? 0 : -ENODEV;\n+}\n+\n+static int qb_spi(int dev, bool save)\n+{\n+\tint ret = 0;\n+\tu32 offset;\n+\tvoid *buf;\n+\tstruct spi_flash *flash;\n+\n+\tif (!CONFIG_IS_ENABLED(SPI)) {\n+\t\tprintf(\"Please enable SPI.\\n\");\n+\t\treturn -EOPNOTSUPP;\n+\t}\n+\n+\tret = qb_spi_find_device(&flash);\n+\tif (ret) {\n+\t\tprintf(\"SPI flash not found.\\n\");\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tret = get_qbdata_offset(flash, QSPI_DEV, &offset);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = spi_flash_erase(flash, offset,\n+\t\t\t      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+\tbuf = (void *)CONFIG_QB_SAVED_STATE_BASE;\n+\tret = spi_flash_write(flash, offset,\n+\t\t\t      QB_STATE_LOAD_SIZE, buf);\n+\n+\treturn ret;\n+}\n+\n+int qb(int qb_dev, bool save)\n+{\n+\tint ret = -EINVAL;\n+\n+\tif (save && !qb_check())\n+\t\treturn ret;\n+\n+\tswitch (qb_dev) {\n+\tcase BOOT_DEVICE_MMC1:\n+\tcase BOOT_DEVICE_MMC2:\n+\tcase BOOT_DEVICE_MMC2_2:\n+\t\tret = qb_mmc(qb_dev, save);\n+\t\tbreak;\n+\tcase BOOT_DEVICE_SPI:\n+\t\tret = qb_spi(qb_dev, save);\n+\t\tbreak;\n+\tdefault:\n+\t\tprintf(\"Unsupported quickboot device\\n\");\n+\t\tbreak;\n+\t}\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, sizeof(struct ddrphy_qb_state));\n+\n+\treturn 0;\n+}\n+\n+void spl_qb_save(void)\n+{\n+\tint dev = spl_boot_device();\n+\n+\t/* Save QB data on current boot device */\n+\tif (qb(dev, 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 fbee435786c..86570e763d7 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 0a45340ffb6..de1f59bfaf5 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  info into memory for low power use.\n \tdefault 0x2051C000\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":["v3","1/6"]}