162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/*====================================================================== 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci drivers/mtd/afs.c: ARM Flash Layout/Partitioning 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci Copyright © 2000 ARM Limited 762306a36Sopenharmony_ci Copyright (C) 2019 Linus Walleij 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci This is access code for flashes using ARM's flash partitioning 1162306a36Sopenharmony_ci standards. 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_ci======================================================================*/ 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/types.h> 1762306a36Sopenharmony_ci#include <linux/kernel.h> 1862306a36Sopenharmony_ci#include <linux/slab.h> 1962306a36Sopenharmony_ci#include <linux/string.h> 2062306a36Sopenharmony_ci#include <linux/init.h> 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 2362306a36Sopenharmony_ci#include <linux/mtd/map.h> 2462306a36Sopenharmony_ci#include <linux/mtd/partitions.h> 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define AFSV1_FOOTER_MAGIC 0xA0FFFF9F 2762306a36Sopenharmony_ci#define AFSV2_FOOTER_MAGIC1 0x464C5348 /* "FLSH" */ 2862306a36Sopenharmony_ci#define AFSV2_FOOTER_MAGIC2 0x464F4F54 /* "FOOT" */ 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistruct footer_v1 { 3162306a36Sopenharmony_ci u32 image_info_base; /* Address of first word of ImageFooter */ 3262306a36Sopenharmony_ci u32 image_start; /* Start of area reserved by this footer */ 3362306a36Sopenharmony_ci u32 signature; /* 'Magic' number proves it's a footer */ 3462306a36Sopenharmony_ci u32 type; /* Area type: ARM Image, SIB, customer */ 3562306a36Sopenharmony_ci u32 checksum; /* Just this structure */ 3662306a36Sopenharmony_ci}; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistruct image_info_v1 { 3962306a36Sopenharmony_ci u32 bootFlags; /* Boot flags, compression etc. */ 4062306a36Sopenharmony_ci u32 imageNumber; /* Unique number, selects for boot etc. */ 4162306a36Sopenharmony_ci u32 loadAddress; /* Address program should be loaded to */ 4262306a36Sopenharmony_ci u32 length; /* Actual size of image */ 4362306a36Sopenharmony_ci u32 address; /* Image is executed from here */ 4462306a36Sopenharmony_ci char name[16]; /* Null terminated */ 4562306a36Sopenharmony_ci u32 headerBase; /* Flash Address of any stripped header */ 4662306a36Sopenharmony_ci u32 header_length; /* Length of header in memory */ 4762306a36Sopenharmony_ci u32 headerType; /* AIF, RLF, s-record etc. */ 4862306a36Sopenharmony_ci u32 checksum; /* Image checksum (inc. this struct) */ 4962306a36Sopenharmony_ci}; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic u32 word_sum(void *words, int num) 5262306a36Sopenharmony_ci{ 5362306a36Sopenharmony_ci u32 *p = words; 5462306a36Sopenharmony_ci u32 sum = 0; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci while (num--) 5762306a36Sopenharmony_ci sum += *p++; 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci return sum; 6062306a36Sopenharmony_ci} 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistatic u32 word_sum_v2(u32 *p, u32 num) 6362306a36Sopenharmony_ci{ 6462306a36Sopenharmony_ci u32 sum = 0; 6562306a36Sopenharmony_ci int i; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci for (i = 0; i < num; i++) { 6862306a36Sopenharmony_ci u32 val; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci val = p[i]; 7162306a36Sopenharmony_ci if (val > ~sum) 7262306a36Sopenharmony_ci sum++; 7362306a36Sopenharmony_ci sum += val; 7462306a36Sopenharmony_ci } 7562306a36Sopenharmony_ci return ~sum; 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic bool afs_is_v1(struct mtd_info *mtd, u_int off) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci /* The magic is 12 bytes from the end of the erase block */ 8162306a36Sopenharmony_ci u_int ptr = off + mtd->erasesize - 12; 8262306a36Sopenharmony_ci u32 magic; 8362306a36Sopenharmony_ci size_t sz; 8462306a36Sopenharmony_ci int ret; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci ret = mtd_read(mtd, ptr, 4, &sz, (u_char *)&magic); 8762306a36Sopenharmony_ci if (ret < 0) { 8862306a36Sopenharmony_ci printk(KERN_ERR "AFS: mtd read failed at 0x%x: %d\n", 8962306a36Sopenharmony_ci ptr, ret); 9062306a36Sopenharmony_ci return false; 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci if (ret >= 0 && sz != 4) 9362306a36Sopenharmony_ci return false; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci return (magic == AFSV1_FOOTER_MAGIC); 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic bool afs_is_v2(struct mtd_info *mtd, u_int off) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci /* The magic is the 8 last bytes of the erase block */ 10162306a36Sopenharmony_ci u_int ptr = off + mtd->erasesize - 8; 10262306a36Sopenharmony_ci u32 foot[2]; 10362306a36Sopenharmony_ci size_t sz; 10462306a36Sopenharmony_ci int ret; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci ret = mtd_read(mtd, ptr, 8, &sz, (u_char *)foot); 10762306a36Sopenharmony_ci if (ret < 0) { 10862306a36Sopenharmony_ci printk(KERN_ERR "AFS: mtd read failed at 0x%x: %d\n", 10962306a36Sopenharmony_ci ptr, ret); 11062306a36Sopenharmony_ci return false; 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci if (ret >= 0 && sz != 8) 11362306a36Sopenharmony_ci return false; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci return (foot[0] == AFSV2_FOOTER_MAGIC1 && 11662306a36Sopenharmony_ci foot[1] == AFSV2_FOOTER_MAGIC2); 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_cistatic int afs_parse_v1_partition(struct mtd_info *mtd, 12062306a36Sopenharmony_ci u_int off, struct mtd_partition *part) 12162306a36Sopenharmony_ci{ 12262306a36Sopenharmony_ci struct footer_v1 fs; 12362306a36Sopenharmony_ci struct image_info_v1 iis; 12462306a36Sopenharmony_ci u_int mask; 12562306a36Sopenharmony_ci /* 12662306a36Sopenharmony_ci * Static checks cannot see that we bail out if we have an error 12762306a36Sopenharmony_ci * reading the footer. 12862306a36Sopenharmony_ci */ 12962306a36Sopenharmony_ci u_int iis_ptr; 13062306a36Sopenharmony_ci u_int img_ptr; 13162306a36Sopenharmony_ci u_int ptr; 13262306a36Sopenharmony_ci size_t sz; 13362306a36Sopenharmony_ci int ret; 13462306a36Sopenharmony_ci int i; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci /* 13762306a36Sopenharmony_ci * This is the address mask; we use this to mask off out of 13862306a36Sopenharmony_ci * range address bits. 13962306a36Sopenharmony_ci */ 14062306a36Sopenharmony_ci mask = mtd->size - 1; 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci ptr = off + mtd->erasesize - sizeof(fs); 14362306a36Sopenharmony_ci ret = mtd_read(mtd, ptr, sizeof(fs), &sz, (u_char *)&fs); 14462306a36Sopenharmony_ci if (ret >= 0 && sz != sizeof(fs)) 14562306a36Sopenharmony_ci ret = -EINVAL; 14662306a36Sopenharmony_ci if (ret < 0) { 14762306a36Sopenharmony_ci printk(KERN_ERR "AFS: mtd read failed at 0x%x: %d\n", 14862306a36Sopenharmony_ci ptr, ret); 14962306a36Sopenharmony_ci return ret; 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci /* 15262306a36Sopenharmony_ci * Check the checksum. 15362306a36Sopenharmony_ci */ 15462306a36Sopenharmony_ci if (word_sum(&fs, sizeof(fs) / sizeof(u32)) != 0xffffffff) 15562306a36Sopenharmony_ci return -EINVAL; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci /* 15862306a36Sopenharmony_ci * Hide the SIB (System Information Block) 15962306a36Sopenharmony_ci */ 16062306a36Sopenharmony_ci if (fs.type == 2) 16162306a36Sopenharmony_ci return 0; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci iis_ptr = fs.image_info_base & mask; 16462306a36Sopenharmony_ci img_ptr = fs.image_start & mask; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* 16762306a36Sopenharmony_ci * Check the image info base. This can not 16862306a36Sopenharmony_ci * be located after the footer structure. 16962306a36Sopenharmony_ci */ 17062306a36Sopenharmony_ci if (iis_ptr >= ptr) 17162306a36Sopenharmony_ci return 0; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci /* 17462306a36Sopenharmony_ci * Check the start of this image. The image 17562306a36Sopenharmony_ci * data can not be located after this block. 17662306a36Sopenharmony_ci */ 17762306a36Sopenharmony_ci if (img_ptr > off) 17862306a36Sopenharmony_ci return 0; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci /* Read the image info block */ 18162306a36Sopenharmony_ci memset(&iis, 0, sizeof(iis)); 18262306a36Sopenharmony_ci ret = mtd_read(mtd, iis_ptr, sizeof(iis), &sz, (u_char *)&iis); 18362306a36Sopenharmony_ci if (ret < 0) { 18462306a36Sopenharmony_ci printk(KERN_ERR "AFS: mtd read failed at 0x%x: %d\n", 18562306a36Sopenharmony_ci iis_ptr, ret); 18662306a36Sopenharmony_ci return -EINVAL; 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci if (sz != sizeof(iis)) 19062306a36Sopenharmony_ci return -EINVAL; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci /* 19362306a36Sopenharmony_ci * Validate the name - it must be NUL terminated. 19462306a36Sopenharmony_ci */ 19562306a36Sopenharmony_ci for (i = 0; i < sizeof(iis.name); i++) 19662306a36Sopenharmony_ci if (iis.name[i] == '\0') 19762306a36Sopenharmony_ci break; 19862306a36Sopenharmony_ci if (i > sizeof(iis.name)) 19962306a36Sopenharmony_ci return -EINVAL; 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci part->name = kstrdup(iis.name, GFP_KERNEL); 20262306a36Sopenharmony_ci if (!part->name) 20362306a36Sopenharmony_ci return -ENOMEM; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci part->size = (iis.length + mtd->erasesize - 1) & ~(mtd->erasesize - 1); 20662306a36Sopenharmony_ci part->offset = img_ptr; 20762306a36Sopenharmony_ci part->mask_flags = 0; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci printk(" mtd: at 0x%08x, %5lluKiB, %8u, %s\n", 21062306a36Sopenharmony_ci img_ptr, part->size / 1024, 21162306a36Sopenharmony_ci iis.imageNumber, part->name); 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci return 0; 21462306a36Sopenharmony_ci} 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_cistatic int afs_parse_v2_partition(struct mtd_info *mtd, 21762306a36Sopenharmony_ci u_int off, struct mtd_partition *part) 21862306a36Sopenharmony_ci{ 21962306a36Sopenharmony_ci u_int ptr; 22062306a36Sopenharmony_ci u32 footer[12]; 22162306a36Sopenharmony_ci u32 imginfo[36]; 22262306a36Sopenharmony_ci char *name; 22362306a36Sopenharmony_ci u32 version; 22462306a36Sopenharmony_ci u32 entrypoint; 22562306a36Sopenharmony_ci u32 attributes; 22662306a36Sopenharmony_ci u32 region_count; 22762306a36Sopenharmony_ci u32 block_start; 22862306a36Sopenharmony_ci u32 block_end; 22962306a36Sopenharmony_ci u32 crc; 23062306a36Sopenharmony_ci size_t sz; 23162306a36Sopenharmony_ci int ret; 23262306a36Sopenharmony_ci int i; 23362306a36Sopenharmony_ci int pad = 0; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci pr_debug("Parsing v2 partition @%08x-%08x\n", 23662306a36Sopenharmony_ci off, off + mtd->erasesize); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci /* First read the footer */ 23962306a36Sopenharmony_ci ptr = off + mtd->erasesize - sizeof(footer); 24062306a36Sopenharmony_ci ret = mtd_read(mtd, ptr, sizeof(footer), &sz, (u_char *)footer); 24162306a36Sopenharmony_ci if ((ret < 0) || (ret >= 0 && sz != sizeof(footer))) { 24262306a36Sopenharmony_ci pr_err("AFS: mtd read failed at 0x%x: %d\n", 24362306a36Sopenharmony_ci ptr, ret); 24462306a36Sopenharmony_ci return -EIO; 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci name = (char *) &footer[0]; 24762306a36Sopenharmony_ci version = footer[9]; 24862306a36Sopenharmony_ci ptr = off + mtd->erasesize - sizeof(footer) - footer[8]; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci pr_debug("found image \"%s\", version %08x, info @%08x\n", 25162306a36Sopenharmony_ci name, version, ptr); 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci /* Then read the image information */ 25462306a36Sopenharmony_ci ret = mtd_read(mtd, ptr, sizeof(imginfo), &sz, (u_char *)imginfo); 25562306a36Sopenharmony_ci if ((ret < 0) || (ret >= 0 && sz != sizeof(imginfo))) { 25662306a36Sopenharmony_ci pr_err("AFS: mtd read failed at 0x%x: %d\n", 25762306a36Sopenharmony_ci ptr, ret); 25862306a36Sopenharmony_ci return -EIO; 25962306a36Sopenharmony_ci } 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_ci /* 32bit platforms have 4 bytes padding */ 26262306a36Sopenharmony_ci crc = word_sum_v2(&imginfo[1], 34); 26362306a36Sopenharmony_ci if (!crc) { 26462306a36Sopenharmony_ci pr_debug("Padding 1 word (4 bytes)\n"); 26562306a36Sopenharmony_ci pad = 1; 26662306a36Sopenharmony_ci } else { 26762306a36Sopenharmony_ci /* 64bit platforms have 8 bytes padding */ 26862306a36Sopenharmony_ci crc = word_sum_v2(&imginfo[2], 34); 26962306a36Sopenharmony_ci if (!crc) { 27062306a36Sopenharmony_ci pr_debug("Padding 2 words (8 bytes)\n"); 27162306a36Sopenharmony_ci pad = 2; 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci if (crc) { 27562306a36Sopenharmony_ci pr_err("AFS: bad checksum on v2 image info: %08x\n", crc); 27662306a36Sopenharmony_ci return -EINVAL; 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci entrypoint = imginfo[pad]; 27962306a36Sopenharmony_ci attributes = imginfo[pad+1]; 28062306a36Sopenharmony_ci region_count = imginfo[pad+2]; 28162306a36Sopenharmony_ci block_start = imginfo[20]; 28262306a36Sopenharmony_ci block_end = imginfo[21]; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci pr_debug("image entry=%08x, attr=%08x, regions=%08x, " 28562306a36Sopenharmony_ci "bs=%08x, be=%08x\n", 28662306a36Sopenharmony_ci entrypoint, attributes, region_count, 28762306a36Sopenharmony_ci block_start, block_end); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci for (i = 0; i < region_count; i++) { 29062306a36Sopenharmony_ci u32 region_load_addr = imginfo[pad + 3 + i*4]; 29162306a36Sopenharmony_ci u32 region_size = imginfo[pad + 4 + i*4]; 29262306a36Sopenharmony_ci u32 region_offset = imginfo[pad + 5 + i*4]; 29362306a36Sopenharmony_ci u32 region_start; 29462306a36Sopenharmony_ci u32 region_end; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci pr_debug(" region %d: address: %08x, size: %08x, " 29762306a36Sopenharmony_ci "offset: %08x\n", 29862306a36Sopenharmony_ci i, 29962306a36Sopenharmony_ci region_load_addr, 30062306a36Sopenharmony_ci region_size, 30162306a36Sopenharmony_ci region_offset); 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci region_start = off + region_offset; 30462306a36Sopenharmony_ci region_end = region_start + region_size; 30562306a36Sopenharmony_ci /* Align partition to end of erase block */ 30662306a36Sopenharmony_ci region_end += (mtd->erasesize - 1); 30762306a36Sopenharmony_ci region_end &= ~(mtd->erasesize -1); 30862306a36Sopenharmony_ci pr_debug(" partition start = %08x, partition end = %08x\n", 30962306a36Sopenharmony_ci region_start, region_end); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci /* Create one partition per region */ 31262306a36Sopenharmony_ci part->name = kstrdup(name, GFP_KERNEL); 31362306a36Sopenharmony_ci if (!part->name) 31462306a36Sopenharmony_ci return -ENOMEM; 31562306a36Sopenharmony_ci part->offset = region_start; 31662306a36Sopenharmony_ci part->size = region_end - region_start; 31762306a36Sopenharmony_ci part->mask_flags = 0; 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci return 0; 32162306a36Sopenharmony_ci} 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_cistatic int parse_afs_partitions(struct mtd_info *mtd, 32462306a36Sopenharmony_ci const struct mtd_partition **pparts, 32562306a36Sopenharmony_ci struct mtd_part_parser_data *data) 32662306a36Sopenharmony_ci{ 32762306a36Sopenharmony_ci struct mtd_partition *parts; 32862306a36Sopenharmony_ci u_int off, sz; 32962306a36Sopenharmony_ci int ret = 0; 33062306a36Sopenharmony_ci int i; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci /* Count the partitions by looping over all erase blocks */ 33362306a36Sopenharmony_ci for (i = off = sz = 0; off < mtd->size; off += mtd->erasesize) { 33462306a36Sopenharmony_ci if (afs_is_v1(mtd, off)) { 33562306a36Sopenharmony_ci sz += sizeof(struct mtd_partition); 33662306a36Sopenharmony_ci i += 1; 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci if (afs_is_v2(mtd, off)) { 33962306a36Sopenharmony_ci sz += sizeof(struct mtd_partition); 34062306a36Sopenharmony_ci i += 1; 34162306a36Sopenharmony_ci } 34262306a36Sopenharmony_ci } 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci if (!i) 34562306a36Sopenharmony_ci return 0; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci parts = kzalloc(sz, GFP_KERNEL); 34862306a36Sopenharmony_ci if (!parts) 34962306a36Sopenharmony_ci return -ENOMEM; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci /* 35262306a36Sopenharmony_ci * Identify the partitions 35362306a36Sopenharmony_ci */ 35462306a36Sopenharmony_ci for (i = off = 0; off < mtd->size; off += mtd->erasesize) { 35562306a36Sopenharmony_ci if (afs_is_v1(mtd, off)) { 35662306a36Sopenharmony_ci ret = afs_parse_v1_partition(mtd, off, &parts[i]); 35762306a36Sopenharmony_ci if (ret) 35862306a36Sopenharmony_ci goto out_free_parts; 35962306a36Sopenharmony_ci i++; 36062306a36Sopenharmony_ci } 36162306a36Sopenharmony_ci if (afs_is_v2(mtd, off)) { 36262306a36Sopenharmony_ci ret = afs_parse_v2_partition(mtd, off, &parts[i]); 36362306a36Sopenharmony_ci if (ret) 36462306a36Sopenharmony_ci goto out_free_parts; 36562306a36Sopenharmony_ci i++; 36662306a36Sopenharmony_ci } 36762306a36Sopenharmony_ci } 36862306a36Sopenharmony_ci 36962306a36Sopenharmony_ci *pparts = parts; 37062306a36Sopenharmony_ci return i; 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ciout_free_parts: 37362306a36Sopenharmony_ci while (--i >= 0) 37462306a36Sopenharmony_ci kfree(parts[i].name); 37562306a36Sopenharmony_ci kfree(parts); 37662306a36Sopenharmony_ci *pparts = NULL; 37762306a36Sopenharmony_ci return ret; 37862306a36Sopenharmony_ci} 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_cistatic const struct of_device_id mtd_parser_afs_of_match_table[] = { 38162306a36Sopenharmony_ci { .compatible = "arm,arm-firmware-suite" }, 38262306a36Sopenharmony_ci {}, 38362306a36Sopenharmony_ci}; 38462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, mtd_parser_afs_of_match_table); 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_cistatic struct mtd_part_parser afs_parser = { 38762306a36Sopenharmony_ci .parse_fn = parse_afs_partitions, 38862306a36Sopenharmony_ci .name = "afs", 38962306a36Sopenharmony_ci .of_match_table = mtd_parser_afs_of_match_table, 39062306a36Sopenharmony_ci}; 39162306a36Sopenharmony_cimodule_mtd_part_parser(afs_parser); 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ciMODULE_AUTHOR("ARM Ltd"); 39462306a36Sopenharmony_ciMODULE_DESCRIPTION("ARM Firmware Suite partition parser"); 39562306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 396