320 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			320 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*======================================================================
 | |
| 
 | |
|     drivers/mtd/maps/integrator-flash.c: ARM Integrator flash map driver
 | |
| 
 | |
|     Copyright (C) 2000 ARM Limited
 | |
|     Copyright (C) 2003 Deep Blue Solutions Ltd.
 | |
| 
 | |
|    This program is free software; you can redistribute it and/or modify
 | |
|    it under the terms of the GNU General Public License as published by
 | |
|    the Free Software Foundation; either version 2 of the License, or
 | |
|    (at your option) any later version.
 | |
| 
 | |
|    This program is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|    GNU General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with this program; if not, write to the Free Software
 | |
|    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | |
| 
 | |
|    This is access code for flashes using ARM's flash partitioning
 | |
|    standards.
 | |
| 
 | |
| ======================================================================*/
 | |
| 
 | |
| #include <linux/module.h>
 | |
| #include <linux/types.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/slab.h>
 | |
| #include <linux/ioport.h>
 | |
| #include <linux/platform_device.h>
 | |
| #include <linux/init.h>
 | |
| #include <linux/io.h>
 | |
| 
 | |
| #include <linux/mtd/mtd.h>
 | |
| #include <linux/mtd/map.h>
 | |
| #include <linux/mtd/partitions.h>
 | |
| #include <linux/mtd/concat.h>
 | |
| 
 | |
| #include <asm/mach/flash.h>
 | |
| #include <mach/hardware.h>
 | |
| #include <asm/system.h>
 | |
| 
 | |
| struct armflash_subdev_info {
 | |
| 	char			*name;
 | |
| 	struct mtd_info		*mtd;
 | |
| 	struct map_info		map;
 | |
| 	struct flash_platform_data *plat;
 | |
| };
 | |
| 
 | |
| struct armflash_info {
 | |
| 	struct resource		*res;
 | |
| 	struct mtd_partition	*parts;
 | |
| 	struct mtd_info		*mtd;
 | |
| 	int			nr_subdev;
 | |
| 	struct armflash_subdev_info subdev[0];
 | |
| };
 | |
| 
 | |
| static void armflash_set_vpp(struct map_info *map, int on)
 | |
| {
 | |
| 	struct armflash_subdev_info *info =
 | |
| 		container_of(map, struct armflash_subdev_info, map);
 | |
| 
 | |
| 	if (info->plat && info->plat->set_vpp)
 | |
| 		info->plat->set_vpp(on);
 | |
| }
 | |
| 
 | |
| static const char *probes[] = { "cmdlinepart", "RedBoot", "afs", NULL };
 | |
| 
 | |
| static int armflash_subdev_probe(struct armflash_subdev_info *subdev,
 | |
| 				 struct resource *res)
 | |
| {
 | |
| 	struct flash_platform_data *plat = subdev->plat;
 | |
| 	resource_size_t size = res->end - res->start + 1;
 | |
| 	void __iomem *base;
 | |
| 	int err = 0;
 | |
| 
 | |
| 	if (!request_mem_region(res->start, size, subdev->name)) {
 | |
| 		err = -EBUSY;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	base = ioremap(res->start, size);
 | |
| 	if (!base) {
 | |
| 		err = -ENOMEM;
 | |
| 		goto no_mem;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * look for CFI based flash parts fitted to this board
 | |
| 	 */
 | |
| 	subdev->map.size	= size;
 | |
| 	subdev->map.bankwidth	= plat->width;
 | |
| 	subdev->map.phys	= res->start;
 | |
| 	subdev->map.virt	= base;
 | |
| 	subdev->map.name	= subdev->name;
 | |
| 	subdev->map.set_vpp	= armflash_set_vpp;
 | |
| 
 | |
| 	simple_map_init(&subdev->map);
 | |
| 
 | |
| 	/*
 | |
| 	 * Also, the CFI layer automatically works out what size
 | |
| 	 * of chips we have, and does the necessary identification
 | |
| 	 * for us automatically.
 | |
| 	 */
 | |
| 	subdev->mtd = do_map_probe(plat->map_name, &subdev->map);
 | |
| 	if (!subdev->mtd) {
 | |
| 		err = -ENXIO;
 | |
| 		goto no_device;
 | |
| 	}
 | |
| 
 | |
| 	subdev->mtd->owner = THIS_MODULE;
 | |
| 
 | |
| 	/* Successful? */
 | |
| 	if (err == 0)
 | |
| 		return err;
 | |
| 
 | |
| 	if (subdev->mtd)
 | |
| 		map_destroy(subdev->mtd);
 | |
|  no_device:
 | |
| 	iounmap(base);
 | |
|  no_mem:
 | |
| 	release_mem_region(res->start, size);
 | |
|  out:
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static void armflash_subdev_remove(struct armflash_subdev_info *subdev)
 | |
| {
 | |
| 	if (subdev->mtd)
 | |
| 		map_destroy(subdev->mtd);
 | |
| 	if (subdev->map.virt)
 | |
| 		iounmap(subdev->map.virt);
 | |
| 	kfree(subdev->name);
 | |
| 	subdev->name = NULL;
 | |
| 	release_mem_region(subdev->map.phys, subdev->map.size);
 | |
| }
 | |
| 
 | |
| static int armflash_probe(struct platform_device *dev)
 | |
| {
 | |
| 	struct flash_platform_data *plat = dev->dev.platform_data;
 | |
| 	unsigned int size;
 | |
| 	struct armflash_info *info;
 | |
| 	int i, nr, err;
 | |
| 
 | |
| 	/* Count the number of devices */
 | |
| 	for (nr = 0; ; nr++)
 | |
| 		if (!platform_get_resource(dev, IORESOURCE_MEM, nr))
 | |
| 			break;
 | |
| 	if (nr == 0) {
 | |
| 		err = -ENODEV;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	size = sizeof(struct armflash_info) +
 | |
| 		sizeof(struct armflash_subdev_info) * nr;
 | |
| 	info = kzalloc(size, GFP_KERNEL);
 | |
| 	if (!info) {
 | |
| 		err = -ENOMEM;
 | |
| 		goto out;
 | |
| 	}
 | |
| 
 | |
| 	if (plat && plat->init) {
 | |
| 		err = plat->init();
 | |
| 		if (err)
 | |
| 			goto no_resource;
 | |
| 	}
 | |
| 
 | |
| 	for (i = 0; i < nr; i++) {
 | |
| 		struct armflash_subdev_info *subdev = &info->subdev[i];
 | |
| 		struct resource *res;
 | |
| 
 | |
| 		res = platform_get_resource(dev, IORESOURCE_MEM, i);
 | |
| 		if (!res)
 | |
| 			break;
 | |
| 
 | |
| 		if (nr == 1)
 | |
| 			/* No MTD concatenation, just use the default name */
 | |
| 			subdev->name = kstrdup(dev_name(&dev->dev), GFP_KERNEL);
 | |
| 		else
 | |
| 			subdev->name = kasprintf(GFP_KERNEL, "%s-%d",
 | |
| 						 dev_name(&dev->dev), i);
 | |
| 		if (!subdev->name) {
 | |
| 			err = -ENOMEM;
 | |
| 			break;
 | |
| 		}
 | |
| 		subdev->plat = plat;
 | |
| 
 | |
| 		err = armflash_subdev_probe(subdev, res);
 | |
| 		if (err) {
 | |
| 			kfree(subdev->name);
 | |
| 			subdev->name = NULL;
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	info->nr_subdev = i;
 | |
| 
 | |
| 	if (err)
 | |
| 		goto subdev_err;
 | |
| 
 | |
| 	if (info->nr_subdev == 1)
 | |
| 		info->mtd = info->subdev[0].mtd;
 | |
| 	else if (info->nr_subdev > 1) {
 | |
| #ifdef CONFIG_MTD_CONCAT
 | |
| 		struct mtd_info *cdev[info->nr_subdev];
 | |
| 
 | |
| 		/*
 | |
| 		 * We detected multiple devices.  Concatenate them together.
 | |
| 		 */
 | |
| 		for (i = 0; i < info->nr_subdev; i++)
 | |
| 			cdev[i] = info->subdev[i].mtd;
 | |
| 
 | |
| 		info->mtd = mtd_concat_create(cdev, info->nr_subdev,
 | |
| 					      dev_name(&dev->dev));
 | |
| 		if (info->mtd == NULL)
 | |
| 			err = -ENXIO;
 | |
| #else
 | |
| 		printk(KERN_ERR "armflash: multiple devices found but "
 | |
| 		       "MTD concat support disabled.\n");
 | |
| 		err = -ENXIO;
 | |
| #endif
 | |
| 	}
 | |
| 
 | |
| 	if (err < 0)
 | |
| 		goto cleanup;
 | |
| 
 | |
| 	err = parse_mtd_partitions(info->mtd, probes, &info->parts, 0);
 | |
| 	if (err > 0) {
 | |
| 		err = add_mtd_partitions(info->mtd, info->parts, err);
 | |
| 		if (err)
 | |
| 			printk(KERN_ERR
 | |
| 			       "mtd partition registration failed: %d\n", err);
 | |
| 	}
 | |
| 
 | |
| 	if (err == 0) {
 | |
| 		platform_set_drvdata(dev, info);
 | |
| 		return err;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * We got an error, free all resources.
 | |
| 	 */
 | |
|  cleanup:
 | |
| 	if (info->mtd) {
 | |
| 		del_mtd_partitions(info->mtd);
 | |
| #ifdef CONFIG_MTD_CONCAT
 | |
| 		if (info->mtd != info->subdev[0].mtd)
 | |
| 			mtd_concat_destroy(info->mtd);
 | |
| #endif
 | |
| 	}
 | |
| 	kfree(info->parts);
 | |
|  subdev_err:
 | |
| 	for (i = info->nr_subdev - 1; i >= 0; i--)
 | |
| 		armflash_subdev_remove(&info->subdev[i]);
 | |
|  no_resource:
 | |
| 	if (plat && plat->exit)
 | |
| 		plat->exit();
 | |
| 	kfree(info);
 | |
|  out:
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static int armflash_remove(struct platform_device *dev)
 | |
| {
 | |
| 	struct armflash_info *info = platform_get_drvdata(dev);
 | |
| 	struct flash_platform_data *plat = dev->dev.platform_data;
 | |
| 	int i;
 | |
| 
 | |
| 	platform_set_drvdata(dev, NULL);
 | |
| 
 | |
| 	if (info) {
 | |
| 		if (info->mtd) {
 | |
| 			del_mtd_partitions(info->mtd);
 | |
| #ifdef CONFIG_MTD_CONCAT
 | |
| 			if (info->mtd != info->subdev[0].mtd)
 | |
| 				mtd_concat_destroy(info->mtd);
 | |
| #endif
 | |
| 		}
 | |
| 		kfree(info->parts);
 | |
| 
 | |
| 		for (i = info->nr_subdev - 1; i >= 0; i--)
 | |
| 			armflash_subdev_remove(&info->subdev[i]);
 | |
| 
 | |
| 		if (plat && plat->exit)
 | |
| 			plat->exit();
 | |
| 
 | |
| 		kfree(info);
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static struct platform_driver armflash_driver = {
 | |
| 	.probe		= armflash_probe,
 | |
| 	.remove		= armflash_remove,
 | |
| 	.driver		= {
 | |
| 		.name	= "armflash",
 | |
| 		.owner	= THIS_MODULE,
 | |
| 	},
 | |
| };
 | |
| 
 | |
| static int __init armflash_init(void)
 | |
| {
 | |
| 	return platform_driver_register(&armflash_driver);
 | |
| }
 | |
| 
 | |
| static void __exit armflash_exit(void)
 | |
| {
 | |
| 	platform_driver_unregister(&armflash_driver);
 | |
| }
 | |
| 
 | |
| module_init(armflash_init);
 | |
| module_exit(armflash_exit);
 | |
| 
 | |
| MODULE_AUTHOR("ARM Ltd");
 | |
| MODULE_DESCRIPTION("ARM Integrator CFI map driver");
 | |
| MODULE_LICENSE("GPL");
 | |
| MODULE_ALIAS("platform:armflash");
 |