109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 21da177e4SLinus Torvalds /* 31da177e4SLinus Torvalds * Flash memory access on SA11x0 based devices 41da177e4SLinus Torvalds * 52f82af08SNicolas Pitre * (C) 2000 Nicolas Pitre <nico@fluxnic.net> 61da177e4SLinus Torvalds */ 71da177e4SLinus Torvalds #include <linux/module.h> 81da177e4SLinus Torvalds #include <linux/types.h> 91da177e4SLinus Torvalds #include <linux/ioport.h> 101da177e4SLinus Torvalds #include <linux/kernel.h> 111da177e4SLinus Torvalds #include <linux/init.h> 121da177e4SLinus Torvalds #include <linux/errno.h> 131da177e4SLinus Torvalds #include <linux/slab.h> 14d052d1beSRussell King #include <linux/platform_device.h> 151da177e4SLinus Torvalds #include <linux/err.h> 1699730225SRussell King #include <linux/io.h> 171da177e4SLinus Torvalds 181da177e4SLinus Torvalds #include <linux/mtd/mtd.h> 191da177e4SLinus Torvalds #include <linux/mtd/map.h> 201da177e4SLinus Torvalds #include <linux/mtd/partitions.h> 211da177e4SLinus Torvalds #include <linux/mtd/concat.h> 221da177e4SLinus Torvalds 23a09e64fbSRussell King #include <mach/hardware.h> 2487dfb311SMasahiro Yamada #include <linux/sizes.h> 251da177e4SLinus Torvalds #include <asm/mach/flash.h> 261da177e4SLinus Torvalds 271da177e4SLinus Torvalds struct sa_subdev_info { 281da177e4SLinus Torvalds char name[16]; 291da177e4SLinus Torvalds struct map_info map; 301da177e4SLinus Torvalds struct mtd_info *mtd; 3157725f0aSRussell King struct flash_platform_data *plat; 321da177e4SLinus Torvalds }; 331da177e4SLinus Torvalds 341da177e4SLinus Torvalds struct sa_info { 351da177e4SLinus Torvalds struct mtd_info *mtd; 361da177e4SLinus Torvalds int num_subdev; 37f1ffdbfaSGustavo A. R. Silva struct sa_subdev_info subdev[]; 381da177e4SLinus Torvalds }; 391da177e4SLinus Torvalds 40ee478af8SPaul Parsons static DEFINE_SPINLOCK(sa1100_vpp_lock); 41ee478af8SPaul Parsons static int sa1100_vpp_refcnt; 421da177e4SLinus Torvalds static void sa1100_set_vpp(struct map_info *map, int on) 431da177e4SLinus Torvalds { 441da177e4SLinus Torvalds struct sa_subdev_info *subdev = container_of(map, struct sa_subdev_info, map); 45ee478af8SPaul Parsons unsigned long flags; 46ee478af8SPaul Parsons 47ee478af8SPaul Parsons spin_lock_irqsave(&sa1100_vpp_lock, flags); 48ee478af8SPaul Parsons if (on) { 49ee478af8SPaul Parsons if (++sa1100_vpp_refcnt == 1) /* first nested 'on' */ 50ee478af8SPaul Parsons subdev->plat->set_vpp(1); 51ee478af8SPaul Parsons } else { 52ee478af8SPaul Parsons if (--sa1100_vpp_refcnt == 0) /* last nested 'off' */ 53ee478af8SPaul Parsons subdev->plat->set_vpp(0); 54ee478af8SPaul Parsons } 55ee478af8SPaul Parsons spin_unlock_irqrestore(&sa1100_vpp_lock, flags); 561da177e4SLinus Torvalds } 571da177e4SLinus Torvalds 581da177e4SLinus Torvalds static void sa1100_destroy_subdev(struct sa_subdev_info *subdev) 591da177e4SLinus Torvalds { 601da177e4SLinus Torvalds if (subdev->mtd) 611da177e4SLinus Torvalds map_destroy(subdev->mtd); 621da177e4SLinus Torvalds if (subdev->map.virt) 631da177e4SLinus Torvalds iounmap(subdev->map.virt); 641da177e4SLinus Torvalds release_mem_region(subdev->map.phys, subdev->map.size); 651da177e4SLinus Torvalds } 661da177e4SLinus Torvalds 671da177e4SLinus Torvalds static int sa1100_probe_subdev(struct sa_subdev_info *subdev, struct resource *res) 681da177e4SLinus Torvalds { 691da177e4SLinus Torvalds unsigned long phys; 701da177e4SLinus Torvalds unsigned int size; 711da177e4SLinus Torvalds int ret; 721da177e4SLinus Torvalds 731da177e4SLinus Torvalds phys = res->start; 741da177e4SLinus Torvalds size = res->end - phys + 1; 751da177e4SLinus Torvalds 761da177e4SLinus Torvalds /* 771da177e4SLinus Torvalds * Retrieve the bankwidth from the MSC registers. 781da177e4SLinus Torvalds * We currently only implement CS0 and CS1 here. 791da177e4SLinus Torvalds */ 801da177e4SLinus Torvalds switch (phys) { 811da177e4SLinus Torvalds default: 821da177e4SLinus Torvalds printk(KERN_WARNING "SA1100 flash: unknown base address " 831da177e4SLinus Torvalds "0x%08lx, assuming CS0\n", phys); 84025a06c1SMiquel Raynal fallthrough; 851da177e4SLinus Torvalds case SA1100_CS0_PHYS: 861da177e4SLinus Torvalds subdev->map.bankwidth = (MSC0 & MSC_RBW) ? 2 : 4; 871da177e4SLinus Torvalds break; 881da177e4SLinus Torvalds 891da177e4SLinus Torvalds case SA1100_CS1_PHYS: 901da177e4SLinus Torvalds subdev->map.bankwidth = ((MSC0 >> 16) & MSC_RBW) ? 2 : 4; 911da177e4SLinus Torvalds break; 921da177e4SLinus Torvalds } 931da177e4SLinus Torvalds 941da177e4SLinus Torvalds if (!request_mem_region(phys, size, subdev->name)) { 951da177e4SLinus Torvalds ret = -EBUSY; 961da177e4SLinus Torvalds goto out; 971da177e4SLinus Torvalds } 981da177e4SLinus Torvalds 9957725f0aSRussell King if (subdev->plat->set_vpp) 1001da177e4SLinus Torvalds subdev->map.set_vpp = sa1100_set_vpp; 1011da177e4SLinus Torvalds 1021da177e4SLinus Torvalds subdev->map.phys = phys; 1031da177e4SLinus Torvalds subdev->map.size = size; 1041da177e4SLinus Torvalds subdev->map.virt = ioremap(phys, size); 1051da177e4SLinus Torvalds if (!subdev->map.virt) { 1061da177e4SLinus Torvalds ret = -ENOMEM; 1071da177e4SLinus Torvalds goto err; 1081da177e4SLinus Torvalds } 1091da177e4SLinus Torvalds 1101da177e4SLinus Torvalds simple_map_init(&subdev->map); 1111da177e4SLinus Torvalds 1121da177e4SLinus Torvalds /* 1131da177e4SLinus Torvalds * Now let's probe for the actual flash. Do it here since 1141da177e4SLinus Torvalds * specific machine settings might have been set above. 1151da177e4SLinus Torvalds */ 11657725f0aSRussell King subdev->mtd = do_map_probe(subdev->plat->map_name, &subdev->map); 1171da177e4SLinus Torvalds if (subdev->mtd == NULL) { 1181da177e4SLinus Torvalds ret = -ENXIO; 1191da177e4SLinus Torvalds goto err; 1201da177e4SLinus Torvalds } 1211da177e4SLinus Torvalds 122794d579aSRussell King printk(KERN_INFO "SA1100 flash: CFI device at 0x%08lx, %uMiB, %d-bit\n", 123794d579aSRussell King phys, (unsigned)(subdev->mtd->size >> 20), 1241da177e4SLinus Torvalds subdev->map.bankwidth * 8); 1251da177e4SLinus Torvalds 1261da177e4SLinus Torvalds return 0; 1271da177e4SLinus Torvalds 1281da177e4SLinus Torvalds err: 1291da177e4SLinus Torvalds sa1100_destroy_subdev(subdev); 1301da177e4SLinus Torvalds out: 1311da177e4SLinus Torvalds return ret; 1321da177e4SLinus Torvalds } 1331da177e4SLinus Torvalds 1340d2ef7d7SRussell King static void sa1100_destroy(struct sa_info *info, struct flash_platform_data *plat) 1351da177e4SLinus Torvalds { 1361da177e4SLinus Torvalds int i; 1371da177e4SLinus Torvalds 1381da177e4SLinus Torvalds if (info->mtd) { 1392fe2e24eSJamie Iles mtd_device_unregister(info->mtd); 1401da177e4SLinus Torvalds if (info->mtd != info->subdev[0].mtd) 1411da177e4SLinus Torvalds mtd_concat_destroy(info->mtd); 1421da177e4SLinus Torvalds } 1431da177e4SLinus Torvalds 1441da177e4SLinus Torvalds for (i = info->num_subdev - 1; i >= 0; i--) 1451da177e4SLinus Torvalds sa1100_destroy_subdev(&info->subdev[i]); 1461da177e4SLinus Torvalds kfree(info); 1470d2ef7d7SRussell King 1480d2ef7d7SRussell King if (plat->exit) 1490d2ef7d7SRussell King plat->exit(); 1501da177e4SLinus Torvalds } 1511da177e4SLinus Torvalds 1527bf350b7SArtem Bityutskiy static struct sa_info *sa1100_setup_mtd(struct platform_device *pdev, 1537bf350b7SArtem Bityutskiy struct flash_platform_data *plat) 1541da177e4SLinus Torvalds { 1551da177e4SLinus Torvalds struct sa_info *info; 1561da177e4SLinus Torvalds int nr, size, i, ret = 0; 1571da177e4SLinus Torvalds 1581da177e4SLinus Torvalds /* 1591da177e4SLinus Torvalds * Count number of devices. 1601da177e4SLinus Torvalds */ 1611da177e4SLinus Torvalds for (nr = 0; ; nr++) 1621da177e4SLinus Torvalds if (!platform_get_resource(pdev, IORESOURCE_MEM, nr)) 1631da177e4SLinus Torvalds break; 1641da177e4SLinus Torvalds 1651da177e4SLinus Torvalds if (nr == 0) { 1661da177e4SLinus Torvalds ret = -ENODEV; 1671da177e4SLinus Torvalds goto out; 1681da177e4SLinus Torvalds } 1691da177e4SLinus Torvalds 1701da177e4SLinus Torvalds size = sizeof(struct sa_info) + sizeof(struct sa_subdev_info) * nr; 1711da177e4SLinus Torvalds 1721da177e4SLinus Torvalds /* 1731da177e4SLinus Torvalds * Allocate the map_info structs in one go. 1741da177e4SLinus Torvalds */ 17595b93a0cSBurman Yan info = kzalloc(size, GFP_KERNEL); 1761da177e4SLinus Torvalds if (!info) { 1771da177e4SLinus Torvalds ret = -ENOMEM; 1781da177e4SLinus Torvalds goto out; 1791da177e4SLinus Torvalds } 1801da177e4SLinus Torvalds 1810d2ef7d7SRussell King if (plat->init) { 1820d2ef7d7SRussell King ret = plat->init(); 1830d2ef7d7SRussell King if (ret) 1840d2ef7d7SRussell King goto err; 1850d2ef7d7SRussell King } 1860d2ef7d7SRussell King 1871da177e4SLinus Torvalds /* 1881da177e4SLinus Torvalds * Claim and then map the memory regions. 1891da177e4SLinus Torvalds */ 1901da177e4SLinus Torvalds for (i = 0; i < nr; i++) { 1911da177e4SLinus Torvalds struct sa_subdev_info *subdev = &info->subdev[i]; 1921da177e4SLinus Torvalds struct resource *res; 1931da177e4SLinus Torvalds 1941da177e4SLinus Torvalds res = platform_get_resource(pdev, IORESOURCE_MEM, i); 1951da177e4SLinus Torvalds if (!res) 1961da177e4SLinus Torvalds break; 1971da177e4SLinus Torvalds 1981da177e4SLinus Torvalds subdev->map.name = subdev->name; 19914e66f76SRussell King sprintf(subdev->name, "%s-%d", plat->name, i); 20057725f0aSRussell King subdev->plat = plat; 2011da177e4SLinus Torvalds 2021da177e4SLinus Torvalds ret = sa1100_probe_subdev(subdev, res); 2031da177e4SLinus Torvalds if (ret) 2041da177e4SLinus Torvalds break; 2051da177e4SLinus Torvalds } 2061da177e4SLinus Torvalds 2071da177e4SLinus Torvalds info->num_subdev = i; 2081da177e4SLinus Torvalds 2091da177e4SLinus Torvalds /* 2101da177e4SLinus Torvalds * ENXIO is special. It means we didn't find a chip when we probed. 2111da177e4SLinus Torvalds */ 2121da177e4SLinus Torvalds if (ret != 0 && !(ret == -ENXIO && info->num_subdev > 0)) 2131da177e4SLinus Torvalds goto err; 2141da177e4SLinus Torvalds 2151da177e4SLinus Torvalds /* 2161da177e4SLinus Torvalds * If we found one device, don't bother with concat support. If 2171da177e4SLinus Torvalds * we found multiple devices, use concat if we have it available, 2181da177e4SLinus Torvalds * otherwise fail. Either way, it'll be called "sa1100". 2191da177e4SLinus Torvalds */ 2201da177e4SLinus Torvalds if (info->num_subdev == 1) { 22114e66f76SRussell King strcpy(info->subdev[0].name, plat->name); 2221da177e4SLinus Torvalds info->mtd = info->subdev[0].mtd; 2231da177e4SLinus Torvalds ret = 0; 2241da177e4SLinus Torvalds } else if (info->num_subdev > 1) { 225ba26cd7dSBoris Brezillon struct mtd_info **cdev; 226ba26cd7dSBoris Brezillon 227ba26cd7dSBoris Brezillon cdev = kmalloc_array(nr, sizeof(*cdev), GFP_KERNEL); 228ba26cd7dSBoris Brezillon if (!cdev) { 229ba26cd7dSBoris Brezillon ret = -ENOMEM; 230ba26cd7dSBoris Brezillon goto err; 231ba26cd7dSBoris Brezillon } 232ba26cd7dSBoris Brezillon 2331da177e4SLinus Torvalds /* 2341da177e4SLinus Torvalds * We detected multiple devices. Concatenate them together. 2351da177e4SLinus Torvalds */ 2361da177e4SLinus Torvalds for (i = 0; i < info->num_subdev; i++) 2371da177e4SLinus Torvalds cdev[i] = info->subdev[i].mtd; 2381da177e4SLinus Torvalds 2391da177e4SLinus Torvalds info->mtd = mtd_concat_create(cdev, info->num_subdev, 24014e66f76SRussell King plat->name); 241ba26cd7dSBoris Brezillon kfree(cdev); 242dc01a28dSDan Carpenter if (info->mtd == NULL) { 2431da177e4SLinus Torvalds ret = -ENXIO; 244dc01a28dSDan Carpenter goto err; 245dc01a28dSDan Carpenter } 2461da177e4SLinus Torvalds } 24772169755SFrans Klaver info->mtd->dev.parent = &pdev->dev; 2481da177e4SLinus Torvalds 2491da177e4SLinus Torvalds if (ret == 0) 2501da177e4SLinus Torvalds return info; 2511da177e4SLinus Torvalds 2521da177e4SLinus Torvalds err: 2530d2ef7d7SRussell King sa1100_destroy(info, plat); 2541da177e4SLinus Torvalds out: 2551da177e4SLinus Torvalds return ERR_PTR(ret); 2561da177e4SLinus Torvalds } 2571da177e4SLinus Torvalds 2580984c891SArtem Bityutskiy static const char * const part_probes[] = { "cmdlinepart", "RedBoot", NULL }; 2591da177e4SLinus Torvalds 26006f25510SBill Pemberton static int sa1100_mtd_probe(struct platform_device *pdev) 2611da177e4SLinus Torvalds { 262d20d5a57SJingoo Han struct flash_platform_data *plat = dev_get_platdata(&pdev->dev); 2631da177e4SLinus Torvalds struct sa_info *info; 264769dc431SDmitry Eremin-Solenikov int err; 2651da177e4SLinus Torvalds 26657725f0aSRussell King if (!plat) 2671da177e4SLinus Torvalds return -ENODEV; 2681da177e4SLinus Torvalds 26957725f0aSRussell King info = sa1100_setup_mtd(pdev, plat); 2701da177e4SLinus Torvalds if (IS_ERR(info)) { 2711da177e4SLinus Torvalds err = PTR_ERR(info); 2721da177e4SLinus Torvalds goto out; 2731da177e4SLinus Torvalds } 2741da177e4SLinus Torvalds 2751da177e4SLinus Torvalds /* 2761da177e4SLinus Torvalds * Partition selection stuff. 2771da177e4SLinus Torvalds */ 27842d7fbe2SArtem Bityutskiy mtd_device_parse_register(info->mtd, part_probes, NULL, plat->parts, 27942d7fbe2SArtem Bityutskiy plat->nr_parts); 280822e5e72SRussell King 2813ae5eaecSRussell King platform_set_drvdata(pdev, info); 2821da177e4SLinus Torvalds err = 0; 2831da177e4SLinus Torvalds 2841da177e4SLinus Torvalds out: 2851da177e4SLinus Torvalds return err; 2861da177e4SLinus Torvalds } 2871da177e4SLinus Torvalds 288*553cc00fSUwe Kleine-König static void sa1100_mtd_remove(struct platform_device *pdev) 2891da177e4SLinus Torvalds { 2903ae5eaecSRussell King struct sa_info *info = platform_get_drvdata(pdev); 291d20d5a57SJingoo Han struct flash_platform_data *plat = dev_get_platdata(&pdev->dev); 2920d2ef7d7SRussell King 2930d2ef7d7SRussell King sa1100_destroy(info, plat); 2941da177e4SLinus Torvalds } 2951da177e4SLinus Torvalds 2963ae5eaecSRussell King static struct platform_driver sa1100_mtd_driver = { 2971da177e4SLinus Torvalds .probe = sa1100_mtd_probe, 298*553cc00fSUwe Kleine-König .remove_new = sa1100_mtd_remove, 2993ae5eaecSRussell King .driver = { 300bcc8f3e0SUwe Kleine-König .name = "sa1100-mtd", 3013ae5eaecSRussell King }, 3021da177e4SLinus Torvalds }; 3031da177e4SLinus Torvalds 304f99640deSAxel Lin module_platform_driver(sa1100_mtd_driver); 3051da177e4SLinus Torvalds 3061da177e4SLinus Torvalds MODULE_AUTHOR("Nicolas Pitre"); 3071da177e4SLinus Torvalds MODULE_DESCRIPTION("SA1100 CFI map driver"); 3081da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 309bcc8f3e0SUwe Kleine-König MODULE_ALIAS("platform:sa1100-mtd"); 310