162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Flash memory access on SA11x0 based devices 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * (C) 2000 Nicolas Pitre <nico@fluxnic.net> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci#include <linux/module.h> 862306a36Sopenharmony_ci#include <linux/types.h> 962306a36Sopenharmony_ci#include <linux/ioport.h> 1062306a36Sopenharmony_ci#include <linux/kernel.h> 1162306a36Sopenharmony_ci#include <linux/init.h> 1262306a36Sopenharmony_ci#include <linux/errno.h> 1362306a36Sopenharmony_ci#include <linux/slab.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/err.h> 1662306a36Sopenharmony_ci#include <linux/io.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <linux/mtd/mtd.h> 1962306a36Sopenharmony_ci#include <linux/mtd/map.h> 2062306a36Sopenharmony_ci#include <linux/mtd/partitions.h> 2162306a36Sopenharmony_ci#include <linux/mtd/concat.h> 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci#include <mach/hardware.h> 2462306a36Sopenharmony_ci#include <linux/sizes.h> 2562306a36Sopenharmony_ci#include <asm/mach/flash.h> 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistruct sa_subdev_info { 2862306a36Sopenharmony_ci char name[16]; 2962306a36Sopenharmony_ci struct map_info map; 3062306a36Sopenharmony_ci struct mtd_info *mtd; 3162306a36Sopenharmony_ci struct flash_platform_data *plat; 3262306a36Sopenharmony_ci}; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistruct sa_info { 3562306a36Sopenharmony_ci struct mtd_info *mtd; 3662306a36Sopenharmony_ci int num_subdev; 3762306a36Sopenharmony_ci struct sa_subdev_info subdev[]; 3862306a36Sopenharmony_ci}; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic DEFINE_SPINLOCK(sa1100_vpp_lock); 4162306a36Sopenharmony_cistatic int sa1100_vpp_refcnt; 4262306a36Sopenharmony_cistatic void sa1100_set_vpp(struct map_info *map, int on) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci struct sa_subdev_info *subdev = container_of(map, struct sa_subdev_info, map); 4562306a36Sopenharmony_ci unsigned long flags; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci spin_lock_irqsave(&sa1100_vpp_lock, flags); 4862306a36Sopenharmony_ci if (on) { 4962306a36Sopenharmony_ci if (++sa1100_vpp_refcnt == 1) /* first nested 'on' */ 5062306a36Sopenharmony_ci subdev->plat->set_vpp(1); 5162306a36Sopenharmony_ci } else { 5262306a36Sopenharmony_ci if (--sa1100_vpp_refcnt == 0) /* last nested 'off' */ 5362306a36Sopenharmony_ci subdev->plat->set_vpp(0); 5462306a36Sopenharmony_ci } 5562306a36Sopenharmony_ci spin_unlock_irqrestore(&sa1100_vpp_lock, flags); 5662306a36Sopenharmony_ci} 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic void sa1100_destroy_subdev(struct sa_subdev_info *subdev) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci if (subdev->mtd) 6162306a36Sopenharmony_ci map_destroy(subdev->mtd); 6262306a36Sopenharmony_ci if (subdev->map.virt) 6362306a36Sopenharmony_ci iounmap(subdev->map.virt); 6462306a36Sopenharmony_ci release_mem_region(subdev->map.phys, subdev->map.size); 6562306a36Sopenharmony_ci} 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_cistatic int sa1100_probe_subdev(struct sa_subdev_info *subdev, struct resource *res) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci unsigned long phys; 7062306a36Sopenharmony_ci unsigned int size; 7162306a36Sopenharmony_ci int ret; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci phys = res->start; 7462306a36Sopenharmony_ci size = res->end - phys + 1; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci /* 7762306a36Sopenharmony_ci * Retrieve the bankwidth from the MSC registers. 7862306a36Sopenharmony_ci * We currently only implement CS0 and CS1 here. 7962306a36Sopenharmony_ci */ 8062306a36Sopenharmony_ci switch (phys) { 8162306a36Sopenharmony_ci default: 8262306a36Sopenharmony_ci printk(KERN_WARNING "SA1100 flash: unknown base address " 8362306a36Sopenharmony_ci "0x%08lx, assuming CS0\n", phys); 8462306a36Sopenharmony_ci fallthrough; 8562306a36Sopenharmony_ci case SA1100_CS0_PHYS: 8662306a36Sopenharmony_ci subdev->map.bankwidth = (MSC0 & MSC_RBW) ? 2 : 4; 8762306a36Sopenharmony_ci break; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci case SA1100_CS1_PHYS: 9062306a36Sopenharmony_ci subdev->map.bankwidth = ((MSC0 >> 16) & MSC_RBW) ? 2 : 4; 9162306a36Sopenharmony_ci break; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci if (!request_mem_region(phys, size, subdev->name)) { 9562306a36Sopenharmony_ci ret = -EBUSY; 9662306a36Sopenharmony_ci goto out; 9762306a36Sopenharmony_ci } 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci if (subdev->plat->set_vpp) 10062306a36Sopenharmony_ci subdev->map.set_vpp = sa1100_set_vpp; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci subdev->map.phys = phys; 10362306a36Sopenharmony_ci subdev->map.size = size; 10462306a36Sopenharmony_ci subdev->map.virt = ioremap(phys, size); 10562306a36Sopenharmony_ci if (!subdev->map.virt) { 10662306a36Sopenharmony_ci ret = -ENOMEM; 10762306a36Sopenharmony_ci goto err; 10862306a36Sopenharmony_ci } 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci simple_map_init(&subdev->map); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci /* 11362306a36Sopenharmony_ci * Now let's probe for the actual flash. Do it here since 11462306a36Sopenharmony_ci * specific machine settings might have been set above. 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_ci subdev->mtd = do_map_probe(subdev->plat->map_name, &subdev->map); 11762306a36Sopenharmony_ci if (subdev->mtd == NULL) { 11862306a36Sopenharmony_ci ret = -ENXIO; 11962306a36Sopenharmony_ci goto err; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci printk(KERN_INFO "SA1100 flash: CFI device at 0x%08lx, %uMiB, %d-bit\n", 12362306a36Sopenharmony_ci phys, (unsigned)(subdev->mtd->size >> 20), 12462306a36Sopenharmony_ci subdev->map.bankwidth * 8); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci return 0; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci err: 12962306a36Sopenharmony_ci sa1100_destroy_subdev(subdev); 13062306a36Sopenharmony_ci out: 13162306a36Sopenharmony_ci return ret; 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic void sa1100_destroy(struct sa_info *info, struct flash_platform_data *plat) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci int i; 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci if (info->mtd) { 13962306a36Sopenharmony_ci mtd_device_unregister(info->mtd); 14062306a36Sopenharmony_ci if (info->mtd != info->subdev[0].mtd) 14162306a36Sopenharmony_ci mtd_concat_destroy(info->mtd); 14262306a36Sopenharmony_ci } 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci for (i = info->num_subdev - 1; i >= 0; i--) 14562306a36Sopenharmony_ci sa1100_destroy_subdev(&info->subdev[i]); 14662306a36Sopenharmony_ci kfree(info); 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci if (plat->exit) 14962306a36Sopenharmony_ci plat->exit(); 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic struct sa_info *sa1100_setup_mtd(struct platform_device *pdev, 15362306a36Sopenharmony_ci struct flash_platform_data *plat) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci struct sa_info *info; 15662306a36Sopenharmony_ci int nr, size, i, ret = 0; 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci /* 15962306a36Sopenharmony_ci * Count number of devices. 16062306a36Sopenharmony_ci */ 16162306a36Sopenharmony_ci for (nr = 0; ; nr++) 16262306a36Sopenharmony_ci if (!platform_get_resource(pdev, IORESOURCE_MEM, nr)) 16362306a36Sopenharmony_ci break; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci if (nr == 0) { 16662306a36Sopenharmony_ci ret = -ENODEV; 16762306a36Sopenharmony_ci goto out; 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci size = sizeof(struct sa_info) + sizeof(struct sa_subdev_info) * nr; 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci /* 17362306a36Sopenharmony_ci * Allocate the map_info structs in one go. 17462306a36Sopenharmony_ci */ 17562306a36Sopenharmony_ci info = kzalloc(size, GFP_KERNEL); 17662306a36Sopenharmony_ci if (!info) { 17762306a36Sopenharmony_ci ret = -ENOMEM; 17862306a36Sopenharmony_ci goto out; 17962306a36Sopenharmony_ci } 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (plat->init) { 18262306a36Sopenharmony_ci ret = plat->init(); 18362306a36Sopenharmony_ci if (ret) 18462306a36Sopenharmony_ci goto err; 18562306a36Sopenharmony_ci } 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci /* 18862306a36Sopenharmony_ci * Claim and then map the memory regions. 18962306a36Sopenharmony_ci */ 19062306a36Sopenharmony_ci for (i = 0; i < nr; i++) { 19162306a36Sopenharmony_ci struct sa_subdev_info *subdev = &info->subdev[i]; 19262306a36Sopenharmony_ci struct resource *res; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci res = platform_get_resource(pdev, IORESOURCE_MEM, i); 19562306a36Sopenharmony_ci if (!res) 19662306a36Sopenharmony_ci break; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci subdev->map.name = subdev->name; 19962306a36Sopenharmony_ci sprintf(subdev->name, "%s-%d", plat->name, i); 20062306a36Sopenharmony_ci subdev->plat = plat; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci ret = sa1100_probe_subdev(subdev, res); 20362306a36Sopenharmony_ci if (ret) 20462306a36Sopenharmony_ci break; 20562306a36Sopenharmony_ci } 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci info->num_subdev = i; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci /* 21062306a36Sopenharmony_ci * ENXIO is special. It means we didn't find a chip when we probed. 21162306a36Sopenharmony_ci */ 21262306a36Sopenharmony_ci if (ret != 0 && !(ret == -ENXIO && info->num_subdev > 0)) 21362306a36Sopenharmony_ci goto err; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci /* 21662306a36Sopenharmony_ci * If we found one device, don't bother with concat support. If 21762306a36Sopenharmony_ci * we found multiple devices, use concat if we have it available, 21862306a36Sopenharmony_ci * otherwise fail. Either way, it'll be called "sa1100". 21962306a36Sopenharmony_ci */ 22062306a36Sopenharmony_ci if (info->num_subdev == 1) { 22162306a36Sopenharmony_ci strcpy(info->subdev[0].name, plat->name); 22262306a36Sopenharmony_ci info->mtd = info->subdev[0].mtd; 22362306a36Sopenharmony_ci ret = 0; 22462306a36Sopenharmony_ci } else if (info->num_subdev > 1) { 22562306a36Sopenharmony_ci struct mtd_info **cdev; 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci cdev = kmalloc_array(nr, sizeof(*cdev), GFP_KERNEL); 22862306a36Sopenharmony_ci if (!cdev) { 22962306a36Sopenharmony_ci ret = -ENOMEM; 23062306a36Sopenharmony_ci goto err; 23162306a36Sopenharmony_ci } 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci /* 23462306a36Sopenharmony_ci * We detected multiple devices. Concatenate them together. 23562306a36Sopenharmony_ci */ 23662306a36Sopenharmony_ci for (i = 0; i < info->num_subdev; i++) 23762306a36Sopenharmony_ci cdev[i] = info->subdev[i].mtd; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci info->mtd = mtd_concat_create(cdev, info->num_subdev, 24062306a36Sopenharmony_ci plat->name); 24162306a36Sopenharmony_ci kfree(cdev); 24262306a36Sopenharmony_ci if (info->mtd == NULL) { 24362306a36Sopenharmony_ci ret = -ENXIO; 24462306a36Sopenharmony_ci goto err; 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci info->mtd->dev.parent = &pdev->dev; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci if (ret == 0) 25062306a36Sopenharmony_ci return info; 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci err: 25362306a36Sopenharmony_ci sa1100_destroy(info, plat); 25462306a36Sopenharmony_ci out: 25562306a36Sopenharmony_ci return ERR_PTR(ret); 25662306a36Sopenharmony_ci} 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_cistatic const char * const part_probes[] = { "cmdlinepart", "RedBoot", NULL }; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_cistatic int sa1100_mtd_probe(struct platform_device *pdev) 26162306a36Sopenharmony_ci{ 26262306a36Sopenharmony_ci struct flash_platform_data *plat = dev_get_platdata(&pdev->dev); 26362306a36Sopenharmony_ci struct sa_info *info; 26462306a36Sopenharmony_ci int err; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci if (!plat) 26762306a36Sopenharmony_ci return -ENODEV; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci info = sa1100_setup_mtd(pdev, plat); 27062306a36Sopenharmony_ci if (IS_ERR(info)) { 27162306a36Sopenharmony_ci err = PTR_ERR(info); 27262306a36Sopenharmony_ci goto out; 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci /* 27662306a36Sopenharmony_ci * Partition selection stuff. 27762306a36Sopenharmony_ci */ 27862306a36Sopenharmony_ci mtd_device_parse_register(info->mtd, part_probes, NULL, plat->parts, 27962306a36Sopenharmony_ci plat->nr_parts); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci platform_set_drvdata(pdev, info); 28262306a36Sopenharmony_ci err = 0; 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci out: 28562306a36Sopenharmony_ci return err; 28662306a36Sopenharmony_ci} 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cistatic int sa1100_mtd_remove(struct platform_device *pdev) 28962306a36Sopenharmony_ci{ 29062306a36Sopenharmony_ci struct sa_info *info = platform_get_drvdata(pdev); 29162306a36Sopenharmony_ci struct flash_platform_data *plat = dev_get_platdata(&pdev->dev); 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci sa1100_destroy(info, plat); 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci return 0; 29662306a36Sopenharmony_ci} 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_cistatic struct platform_driver sa1100_mtd_driver = { 29962306a36Sopenharmony_ci .probe = sa1100_mtd_probe, 30062306a36Sopenharmony_ci .remove = sa1100_mtd_remove, 30162306a36Sopenharmony_ci .driver = { 30262306a36Sopenharmony_ci .name = "sa1100-mtd", 30362306a36Sopenharmony_ci }, 30462306a36Sopenharmony_ci}; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_cimodule_platform_driver(sa1100_mtd_driver); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ciMODULE_AUTHOR("Nicolas Pitre"); 30962306a36Sopenharmony_ciMODULE_DESCRIPTION("SA1100 CFI map driver"); 31062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 31162306a36Sopenharmony_ciMODULE_ALIAS("platform:sa1100-mtd"); 312