BootROM of Amlogic SoCs that use ADNL/Optimus protocols needs
special layout of "bootloader" partition. So let's implement
functions that support bootloader writing on such SoCs. This is
prerequisite for ADNL/Optimus implementation.

We place such functions to 'arch/arm/mach-meson', because this
code is also needed by 'fastboot' protocol which can also write
"bootloader" partition.

Signed-off-by: Arseniy Krasnov <avkras...@salutedevices.com>
---
 arch/arm/include/asm/arch-meson/nand.h    |  34 +++
 arch/arm/include/asm/arch-meson/spinand.h |  43 ++++
 arch/arm/mach-meson/Kconfig               |  31 +++
 arch/arm/mach-meson/Makefile              |   4 +-
 arch/arm/mach-meson/rawnand.c             | 291 ++++++++++++++++++++++
 arch/arm/mach-meson/spinand.c             | 158 ++++++++++++
 6 files changed, 559 insertions(+), 2 deletions(-)
 create mode 100644 arch/arm/include/asm/arch-meson/nand.h
 create mode 100644 arch/arm/include/asm/arch-meson/spinand.h
 create mode 100644 arch/arm/mach-meson/rawnand.c
 create mode 100644 arch/arm/mach-meson/spinand.c

diff --git a/arch/arm/include/asm/arch-meson/nand.h 
b/arch/arm/include/asm/arch-meson/nand.h
new file mode 100644
index 00000000000..1f5a20d237b
--- /dev/null
+++ b/arch/arm/include/asm/arch-meson/nand.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2023-2024 SaluteDevices, Inc.
+ */
+
+#ifndef __MESON_NAND_H__
+#define __MESON_NAND_H__
+
+#define BOOT_LOADER     "bootloader"
+#define BOOT_BL2        "bl2"
+#define BOOT_SPL        "spl"
+#define BOOT_TPL        "tpl"
+#define BOOT_FIP        "fip"
+
+#define BL2_COPY_NUM            (CONFIG_MESON_BL2_COPY_NUM)
+#define BL2_IMAGE_SIZE_PER_COPY (CONFIG_MESON_BL2_IMAGE_SIZE)
+#define BL2_TOTAL_PAGES         1024
+
+#define TPL_COPY_NUM            (CONFIG_MESON_TPL_COPY_NUM)
+#define TPL_SIZE_PER_COPY       0x200000
+
+#define NAND_RSV_BLOCK_NUM      48
+
+/**
+ * meson_bootloader_write - write 'bootloader' partition to NAND
+ *                          according to the required layout. It will
+ *                          write BL2, TPL and info pages.
+ *
+ * @buf: buffer with BL2 and TPL
+ * @length: buffer length
+ * @return: 0 on success, -errno otherwise.
+ */
+int meson_bootloader_write(unsigned char *buf, size_t length);
+#endif
diff --git a/arch/arm/include/asm/arch-meson/spinand.h 
b/arch/arm/include/asm/arch-meson/spinand.h
new file mode 100644
index 00000000000..1628c8bd4e0
--- /dev/null
+++ b/arch/arm/include/asm/arch-meson/spinand.h
@@ -0,0 +1,43 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * (C) Copyright 2024 SaluteDevices, Inc.
+ */
+
+#ifndef __MESON_SPINAND_H__
+#define __MESON_SPINAND_H__
+
+#include <stdint.h>
+#include <string.h>
+#include <asm/types.h>
+#include <asm/arch-meson/nand.h>
+#include <linux/compiler_attributes.h>
+#include <linux/mtd/mtd.h>
+
+struct meson_spi_nand_info_page {
+       char magic[8];  /* Magic header of info page. */
+       /* Info page version, +1 when you update this struct. */
+       u8 version;     /* 1 for now. */
+       u8 mode;        /* 1 discrete, 0 compact. */
+       u8 bl2_num;     /* bl2 copy number. */
+       u8 fip_num;     /* fip copy number. */
+       union {
+               struct {
+                       u8 rd_max;              /* spi nand max read io. */
+                       u8 oob_offset;          /* User bytes offset. */
+                       u8 planes_per_lun;      /* number of planes per LUN. */
+                       u8 rsv;                 /* Reserved gap. */
+                       u32 fip_start;          /* Start pages. */
+                       u32 fip_pages;          /* Pages per fip. */
+                       u32 page_size;          /* spi nand page size (bytes). 
*/
+                       u32 page_per_blk;       /* Page number per block. */
+                       u32 oob_size;           /* Valid oob size (bytes). */
+                       u32 bbt_start;          /* BBT start pages. */
+                       u32 bbt_valid;          /* BBT valid offset pages. */
+                       u32 bbt_size;           /* BBT occupied bytes. */
+               } __packed spinand;             /* spi nand. */
+               struct {
+                       u32 reserved;
+               } emmc;
+       } dev;
+} __packed;
+#endif
diff --git a/arch/arm/mach-meson/Kconfig b/arch/arm/mach-meson/Kconfig
index 7570f48e25f..883ee78f49b 100644
--- a/arch/arm/mach-meson/Kconfig
+++ b/arch/arm/mach-meson/Kconfig
@@ -93,4 +93,35 @@ config SYS_BOARD
          Based on this option board/<CONFIG_SYS_VENDOR>/<CONFIG_SYS_BOARD> will
          be used.
 
+config MESON_BL2_COPY_NUM
+       depends on ADNL || FASTBOOT_FLASH
+       int "Number of BL2 copies written to storage"
+       default 8
+       help
+               The ADNL / Optimus / fastboot protocol writes several copies of 
BL2
+               bootloader during firmware update process.
+
+config MESON_TPL_COPY_NUM
+       depends on ADNL || FASTBOOT_FLASH
+       int "Number of TPL copies written to storage"
+       default 4
+       help
+               The ADNL / Optimus / fastboot protocol writes several copies of 
TPL
+               bootloader during firmware update process.
+
+config MESON_BL2_IMAGE_SIZE
+       depends on ADNL || FASTBOOT_FLASH
+       hex "Size of BL2 image"
+       default 0x10000
+       help
+               Size of BL2 image, passed by ADNL / Optimus / fastboot 
protocols.
+
+config MESON_BL2_IMAGE_OFFSET
+       depends on ADNL || FASTBOOT_FLASH
+       hex "Offset from info page to BL2 image"
+       default 0x800
+       help
+               For each copy of BL2, this is offset from info page to BL2 
image. Used
+               by Optimus and fastboot protocols.
+
 endif
diff --git a/arch/arm/mach-meson/Makefile b/arch/arm/mach-meson/Makefile
index 535b0878b91..f63f91dde35 100644
--- a/arch/arm/mach-meson/Makefile
+++ b/arch/arm/mach-meson/Makefile
@@ -4,6 +4,6 @@
 
 obj-y += board-common.o sm.o board-info.o
 obj-$(CONFIG_MESON_GX) += board-gx.o
-obj-$(CONFIG_MESON_AXG) += board-axg.o
+obj-$(CONFIG_MESON_AXG) += board-axg.o rawnand.o
 obj-$(CONFIG_MESON_G12A) += board-g12a.o
-obj-$(CONFIG_MESON_A1) += board-a1.o
+obj-$(CONFIG_MESON_A1) += board-a1.o spinand.o
diff --git a/arch/arm/mach-meson/rawnand.c b/arch/arm/mach-meson/rawnand.c
new file mode 100644
index 00000000000..6b91674200e
--- /dev/null
+++ b/arch/arm/mach-meson/rawnand.c
@@ -0,0 +1,291 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2024 SaluteDevices, Inc.
+ */
+
+#include <nand.h>
+#include <asm/types.h>
+#include <asm/arch/rawnand.h>
+#include <linux/compiler_attributes.h>
+#include <linux/mtd/mtd.h>
+#include <linux/sizes.h>
+
+struct raw_nand_setup {
+       union {
+               u32 d32;
+               struct {
+                       unsigned cmd:22;        /* NAND controller cmd to read. 
*/
+                       unsigned large_page:1;  /* Page is large. */
+                       unsigned no_rb:1;       /* No RB. */
+                       unsigned a2:1;          /* A2 cmd enable flag. */
+                       unsigned reserved25:1;  /* Gap. */
+                       unsigned page_list:1;   /* Page list is enabled. */
+                       unsigned sync_mode:2;   /* Always 0 according vendor 
doc. */
+                       unsigned size:2;        /* Always 0 according vendor 
doc. */
+                       unsigned active:1;      /* Always 0 according vendor 
doc. */
+               } b;
+       } cfg;
+       u16 id;
+       u16 max;
+} __packed;
+
+struct raw_nand_cmd {
+       unsigned char type;
+       unsigned char val;
+} __packed;
+
+struct raw_ext_info {
+       u32 read_info;
+       u32 new_type;
+       u32 page_per_blk;
+       u32 xlc;
+       u32 ce_mask;
+       u32 boot_num;
+       u32 each_boot_pages;
+       /* for compatible reason */
+       u32 bbt_occupy_pages;
+       u32 bbt_start_block;
+} __packed;
+
+struct raw_fip_info {
+       u16 version;
+       u16 mode;
+       u32 fip_start;
+} __packed;
+
+struct raw_nand_info_page {
+       struct raw_nand_setup nand_setup;
+       unsigned char page_list[16];
+       struct raw_nand_cmd retry_usr[32];
+       struct raw_ext_info ext_info;
+       struct raw_fip_info fip_info;
+       u32 ddrp_start_page;
+} __packed;
+
+static void meson_raw_fill_oobbuf_with_magic(u8 *oobbuf, size_t oobavail)
+{
+       for (size_t j = 0; j < oobavail; j += 2) {
+               oobbuf[j] = 0x55;
+               oobbuf[j + 1] = 0xaa;
+       }
+}
+
+static int meson_raw_raw_nand_info_page_prepare(struct mtd_info *mtd, void 
*oobbuf)
+{
+       struct nand_chip *nand_chip = mtd_to_nand(mtd->parent);
+       struct raw_nand_info_page *p_nand_page0;
+       struct raw_ext_info *ext_info;
+       struct raw_nand_setup *nand_setup;
+       struct raw_fip_info *fip_info;
+       u32 config_data;
+
+       p_nand_page0 = oobbuf;
+       memset(p_nand_page0, 0x0, sizeof(struct raw_nand_info_page));
+       nand_setup = &p_nand_page0->nand_setup;
+       ext_info   = &p_nand_page0->ext_info;
+
+       config_data = CMDRWGEN(NFC_CMD_N2M, 0, 1, 0, nand_chip->ecc.size >> 3,
+                              nand_chip->ecc.steps);
+
+       config_data = config_data | BIT(23) | BIT(22) | BIT(21);
+       nand_setup->cfg.d32 = config_data;
+       nand_setup->id = 0;
+       nand_setup->max = 0;
+
+       ext_info->read_info = 1;
+       ext_info->page_per_blk = mtd->erasesize / mtd->writesize;
+       ext_info->ce_mask = 0x01;
+       ext_info->xlc = 1;
+       ext_info->boot_num = TPL_COPY_NUM;
+       ext_info->each_boot_pages = TPL_SIZE_PER_COPY / mtd->writesize;
+       ext_info->bbt_occupy_pages = 0x1;
+       ext_info->bbt_start_block = 0x14; /* Amlogic keeps BBT in hardcoded 
offset. */
+
+       p_nand_page0->ddrp_start_page = 0xb00;
+
+       fip_info = &p_nand_page0->fip_info;
+       fip_info->version = 1;
+       fip_info->mode = 1; /* Discrete mode. */
+       fip_info->fip_start = BL2_TOTAL_PAGES +
+               (NAND_RSV_BLOCK_NUM * ext_info->page_per_blk);
+
+       return 0;
+}
+
+static int meson_raw_write_info_page(struct mtd_info *mtd, loff_t offset, 
size_t *actual)
+{
+       int ret;
+       struct mtd_oob_ops ops;
+       u8 *datbuf = (u8 *)calloc(mtd->writesize, sizeof(u8));
+
+       if (!datbuf) {
+               pr_err("Failed to allocate data buf for info page\n");
+               return -ENOMEM;
+       }
+
+       meson_raw_raw_nand_info_page_prepare(mtd, datbuf);
+
+       memset(&ops, 0, sizeof(struct mtd_oob_ops));
+       ops.len = mtd->writesize;
+       ops.datbuf = datbuf;
+       ops.ooblen = mtd->oobavail;
+       ops.mode = MTD_OPS_AUTO_OOB;
+       ops.oobbuf = calloc(mtd->oobavail, sizeof(u8));
+
+       if (!ops.oobbuf) {
+               pr_err("Failed to allocate OOB buf for info page\n");
+               free(datbuf);
+               return -ENOMEM;
+       }
+
+       meson_raw_fill_oobbuf_with_magic(ops.oobbuf, mtd->oobavail);
+
+       ret = mtd_write_oob(mtd, offset, &ops);
+       *actual = ops.retlen;
+
+       free(ops.oobbuf);
+       free(datbuf);
+
+       return ret;
+}
+
+static int meson_raw_write_bl2(unsigned char *src_data)
+{
+       int ret;
+       size_t i;
+       nand_erase_options_t opts;
+       struct mtd_info *mtd;
+       size_t bl2_copy_limit;
+       struct nand_chip *nand;
+       size_t oobbuff_size;
+       struct mtd_oob_ops ops;
+       size_t src_size = BL2_IMAGE_SIZE_PER_COPY;
+       loff_t dest = 0;
+       size_t copy_num = BL2_COPY_NUM;
+
+       pr_info("Writing BL2\n");
+
+       mtd = get_mtd_device_nm(BOOT_LOADER);
+       if (IS_ERR_OR_NULL(mtd)) {
+               pr_err("Failed to get mtd device \"bootloader\" ret: %ld\n", 
PTR_ERR(mtd));
+               return PTR_ERR(mtd);
+       }
+
+       if (mtd->oobavail & 1) {
+               pr_err("Invalid OOB available bytes: %u\n", mtd->oobavail);
+               return -ENODEV;
+       }
+
+       memset(&opts, 0, sizeof(opts));
+       opts.offset = dest;
+       opts.length = mtd->size;
+       if (nand_erase_opts(mtd, &opts)) {
+               pr_err("Failed to erase \"bootloader\"\n");
+               return -EIO;
+       }
+
+       bl2_copy_limit = mtd->size / BL2_COPY_NUM;
+
+       memset(&ops, 0, sizeof(struct mtd_oob_ops));
+       ops.len = src_size;
+       ops.datbuf = src_data;
+       ops.ooblen = mtd->oobavail * (src_size / mtd->writesize + 1);
+       ops.mode = MTD_OPS_AUTO_OOB;
+
+       nand = mtd_to_nand(mtd->parent);
+       oobbuff_size = (mtd->writesize / nand->ecc.size) * ops.ooblen;
+
+       ops.oobbuf = calloc(oobbuff_size, sizeof(u8));
+
+       if (!ops.oobbuf) {
+               pr_err("Failed to allocate OOB buf for BL2\n");
+               return -ENOMEM;
+       }
+
+       meson_raw_fill_oobbuf_with_magic(ops.oobbuf, ops.ooblen);
+
+       for (i = 0; i < copy_num; i++) {
+               size_t actual = 0;
+               loff_t offset_in_partition = dest + i * bl2_copy_limit;
+
+               if (mtd_block_isbad(mtd, offset_in_partition)) {
+                       pr_info("Skip badblock at %llx\n", offset_in_partition);
+                       continue;
+               }
+
+               ret = meson_raw_write_info_page(mtd, offset_in_partition, 
&actual);
+               if (ret)
+                       goto free_oobbuf;
+
+               offset_in_partition += CONFIG_MESON_BL2_IMAGE_OFFSET +
+                                      (actual - mtd->writesize);
+               ret = mtd_write_oob(mtd, offset_in_partition, &ops);
+               if (ret)
+                       goto free_oobbuf;
+       }
+
+free_oobbuf:
+       free(ops.oobbuf);
+
+       return ret;
+}
+
+static int meson_raw_write_tpl(unsigned char *src_data, size_t src_size)
+{
+       int ret;
+       size_t i;
+       struct mtd_info *mtd;
+       nand_erase_options_t opts;
+       loff_t dest = 0;
+       size_t copy_num = TPL_COPY_NUM;
+
+       pr_info("Writing TPL\n");
+       mtd = get_mtd_device_nm(BOOT_TPL);
+
+       if (IS_ERR_OR_NULL(mtd)) {
+               pr_err("Failed to get mtd device \"tpl\" ret: %ld\n", 
PTR_ERR(mtd));
+               return PTR_ERR(mtd);
+       }
+
+       memset(&opts, 0, sizeof(nand_erase_options_t));
+       opts.offset = dest;
+       opts.length = mtd->size;
+       if (nand_erase_opts(mtd, &opts)) {
+               pr_err("Failed to erase \"tpl\"\n");
+               return -EIO;
+       }
+
+       for (i = 0; i < copy_num; i++) {
+               loff_t offset_in_partition = dest + i * TPL_SIZE_PER_COPY;
+
+               ret = nand_write_skip_bad(mtd, offset_in_partition, &src_size,
+                                         NULL, mtd->size / TPL_COPY_NUM, 
src_data, 0);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
+
+int meson_bootloader_write(unsigned char *buf, size_t length)
+{
+       int ret;
+
+       if (!buf) {
+               pr_err("Empty buffer is received\n");
+               return -EINVAL;
+       }
+
+       ret = meson_raw_write_bl2(buf);
+       if (ret)
+               return ret;
+
+       /* Write FIP. FIP is also called TPL */
+       ret = meson_raw_write_tpl(buf + BL2_IMAGE_SIZE_PER_COPY,
+                                 length - BL2_IMAGE_SIZE_PER_COPY);
+       if (ret)
+               return ret;
+
+       pr_info("\"Bootloader (BL2+TPL)\" is written\n");
+       return 0;
+}
diff --git a/arch/arm/mach-meson/spinand.c b/arch/arm/mach-meson/spinand.c
new file mode 100644
index 00000000000..dbad4ba0e9f
--- /dev/null
+++ b/arch/arm/mach-meson/spinand.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * (C) Copyright 2023 SaluteDevices, Inc.
+ */
+
+#include <nand.h>
+#include <asm/arch/spinand.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/sizes.h>
+
+#define SPINAND_MAGIC          "AMLIFPG"
+#define SPINAND_INFO_VER       1
+
+static void meson_bootloader_setup_info_page(struct meson_spi_nand_info_page 
*info_page,
+                                            struct mtd_info *mtd)
+{
+       struct nand_device *nand_dev;
+       u32 page_per_blk;
+
+       nand_dev = mtd_to_nanddev(mtd->parent);
+       page_per_blk = mtd->erasesize / mtd->writesize;
+       info_page->version = SPINAND_INFO_VER;
+       /* DISCRETE only */
+       info_page->mode = 1;
+       info_page->bl2_num = BL2_COPY_NUM;
+       info_page->fip_num = TPL_COPY_NUM;
+       info_page->dev.spinand.rd_max = 2;
+       info_page->dev.spinand.fip_start = BL2_TOTAL_PAGES + NAND_RSV_BLOCK_NUM 
* page_per_blk;
+       info_page->dev.spinand.fip_pages = TPL_SIZE_PER_COPY / mtd->writesize;
+       info_page->dev.spinand.page_size = mtd->writesize;
+       info_page->dev.spinand.page_per_blk = page_per_blk;
+       info_page->dev.spinand.oob_size = mtd->oobsize;
+       info_page->dev.spinand.oob_offset = 0;
+       info_page->dev.spinand.bbt_start = 0;
+       info_page->dev.spinand.bbt_valid = 0;
+       info_page->dev.spinand.bbt_size = 0;
+       info_page->dev.spinand.planes_per_lun = nand_dev->memorg.planes_per_lun;
+}
+
+static int meson_store_boot_write_bl2(struct mtd_info *mtd, void *buf, 
unsigned int copy_idx,
+                                     struct meson_spi_nand_info_page 
*info_page)
+{
+        unsigned int size_per_copy;
+       loff_t offset;
+       size_t retlen;
+       int ret;
+
+       size_per_copy = mtd->writesize * (BL2_TOTAL_PAGES / BL2_COPY_NUM);
+       offset = mtd->offset + (copy_idx * size_per_copy);
+       ret = mtd_write(mtd, offset, BL2_IMAGE_SIZE_PER_COPY, &retlen, buf);
+       if (ret)
+               return ret;
+
+       /*
+        * Info page is written directly after each BL2 image. For
+        * example let:
+        * BL2_TOTAL_PAGES = 1024
+        * BL2_COPY_NUM = 8
+        * BL2_IMAGE_SIZE_PER_COPY = 65536 = 0x10000
+        * size_per_copy = 2048 * (1024 / 8) = 0x40000
+        *
+        * BL2 image will be written to BL2_COPY_NUM
+        * locations:
+        * 0x00000
+        * 0x40000
+        * 0x80000
+        * 0xc0000
+        * 0x100000
+        * 0x140000
+        * 0x180000
+        * 0x1c0000
+        *
+        * Info pages will be written to BL2_COPY_NUM
+        * locations:
+        * 0x10000
+        * 0x50000
+        * 0x90000
+        * 0xd0000
+        * 0x110000
+        * 0x150000
+        * 0x190000
+        * 0x1d0000
+        *
+        * Here is the same, in picture:
+        *
+        *          BL2_TOTAL_PAGES == 1024
+        *             BL2_COPY_NUM == 8
+        *  /---------------------------------------\
+        *  |                                       |
+        *
+        *  |AB  |AB  |AB  |AB  |AB  |AB  |AB  |AB  |
+        *  |____|____|____|____|____|____|____|____|
+        *  ^    ^
+        *  |    |
+        *  |____|__ size_per_copy == 0x40000
+        *
+        *  A - is BL2 image. It is BL2_IMAGE_SIZE_PER_COPY == 0x10000 bytes.
+        *  B - is info page.
+        */
+       offset += BL2_IMAGE_SIZE_PER_COPY;
+
+       return mtd_write(mtd, offset, sizeof(*info_page),
+                        &retlen, (u8 *)info_page);
+}
+
+static int meson_store_boot_write_tpl(void *buf, size_t size, unsigned int 
copy_idx)
+{
+       loff_t offset;
+       struct mtd_info *mtd;
+       loff_t limit;
+       size_t retlen = 0;
+
+       mtd = get_mtd_device_nm(BOOT_TPL);
+       if (IS_ERR_OR_NULL(mtd))
+               return -ENODEV;
+
+       offset = mtd->offset + (copy_idx * TPL_SIZE_PER_COPY);
+       limit = offset + TPL_SIZE_PER_COPY;
+
+       return nand_write_skip_bad(mtd->parent, offset, &size, &retlen,
+                                  limit, buf, 0);
+}
+
+int meson_bootloader_write(unsigned char *buf, size_t length)
+{
+       struct meson_spi_nand_info_page info_page;
+       unsigned int copy_idx;
+       struct mtd_info *mtd;
+       int ret;
+
+       /* 'buf' contains BL2 and TPL. If there is only BL2 or
+        * even less - input data is invalid.
+        */
+       if (length <= BL2_IMAGE_SIZE_PER_COPY)
+               return -EINVAL;
+
+       mtd = get_mtd_device_nm(BOOT_LOADER);
+       if (IS_ERR_OR_NULL(mtd))
+               return -ENODEV;
+
+       meson_bootloader_setup_info_page(&info_page, mtd);
+
+       for (copy_idx = 0; copy_idx < BL2_COPY_NUM; copy_idx++) {
+               ret = meson_store_boot_write_bl2(mtd, buf, copy_idx, 
&info_page);
+               if (ret)
+                       return ret;
+       }
+
+       for (copy_idx = 0; copy_idx < TPL_COPY_NUM; copy_idx++) {
+               ret = meson_store_boot_write_tpl(buf + BL2_IMAGE_SIZE_PER_COPY,
+                                                length - 
BL2_IMAGE_SIZE_PER_COPY, copy_idx);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
+}
-- 
2.30.1

Reply via email to