xref: /openbmc/linux/drivers/sbus/char/flash.c (revision fd098316)
1a9540d34SDavid S. Miller /* flash.c: Allow mmap access to the OBP Flash, for OBP updates.
21da177e4SLinus Torvalds  *
31da177e4SLinus Torvalds  * Copyright (C) 1997  Eddie C. Dost  (ecd@skynet.be)
41da177e4SLinus Torvalds  */
51da177e4SLinus Torvalds 
61da177e4SLinus Torvalds #include <linux/module.h>
71da177e4SLinus Torvalds #include <linux/types.h>
81da177e4SLinus Torvalds #include <linux/errno.h>
91da177e4SLinus Torvalds #include <linux/miscdevice.h>
101da177e4SLinus Torvalds #include <linux/slab.h>
111da177e4SLinus Torvalds #include <linux/fcntl.h>
121da177e4SLinus Torvalds #include <linux/poll.h>
131da177e4SLinus Torvalds #include <linux/init.h>
141da177e4SLinus Torvalds #include <linux/smp_lock.h>
151da177e4SLinus Torvalds #include <linux/spinlock.h>
168a73709eSHorst H. von Brand #include <linux/mm.h>
17a9540d34SDavid S. Miller #include <linux/of.h>
18a9540d34SDavid S. Miller #include <linux/of_device.h>
191da177e4SLinus Torvalds 
201da177e4SLinus Torvalds #include <asm/system.h>
211da177e4SLinus Torvalds #include <asm/uaccess.h>
221da177e4SLinus Torvalds #include <asm/pgtable.h>
231da177e4SLinus Torvalds #include <asm/io.h>
241da177e4SLinus Torvalds #include <asm/upa.h>
251da177e4SLinus Torvalds 
261da177e4SLinus Torvalds static DEFINE_SPINLOCK(flash_lock);
271da177e4SLinus Torvalds static struct {
281da177e4SLinus Torvalds 	unsigned long read_base;	/* Physical read address */
291da177e4SLinus Torvalds 	unsigned long write_base;	/* Physical write address */
301da177e4SLinus Torvalds 	unsigned long read_size;	/* Size of read area */
311da177e4SLinus Torvalds 	unsigned long write_size;	/* Size of write area */
321da177e4SLinus Torvalds 	unsigned long busy;		/* In use? */
331da177e4SLinus Torvalds } flash;
341da177e4SLinus Torvalds 
351da177e4SLinus Torvalds #define FLASH_MINOR	152
361da177e4SLinus Torvalds 
371da177e4SLinus Torvalds static int
381da177e4SLinus Torvalds flash_mmap(struct file *file, struct vm_area_struct *vma)
391da177e4SLinus Torvalds {
401da177e4SLinus Torvalds 	unsigned long addr;
411da177e4SLinus Torvalds 	unsigned long size;
421da177e4SLinus Torvalds 
431da177e4SLinus Torvalds 	spin_lock(&flash_lock);
441da177e4SLinus Torvalds 	if (flash.read_base == flash.write_base) {
451da177e4SLinus Torvalds 		addr = flash.read_base;
461da177e4SLinus Torvalds 		size = flash.read_size;
471da177e4SLinus Torvalds 	} else {
481da177e4SLinus Torvalds 		if ((vma->vm_flags & VM_READ) &&
491da177e4SLinus Torvalds 		    (vma->vm_flags & VM_WRITE)) {
501da177e4SLinus Torvalds 			spin_unlock(&flash_lock);
511da177e4SLinus Torvalds 			return -EINVAL;
521da177e4SLinus Torvalds 		}
531da177e4SLinus Torvalds 		if (vma->vm_flags & VM_READ) {
541da177e4SLinus Torvalds 			addr = flash.read_base;
551da177e4SLinus Torvalds 			size = flash.read_size;
561da177e4SLinus Torvalds 		} else if (vma->vm_flags & VM_WRITE) {
571da177e4SLinus Torvalds 			addr = flash.write_base;
581da177e4SLinus Torvalds 			size = flash.write_size;
591da177e4SLinus Torvalds 		} else {
601da177e4SLinus Torvalds 			spin_unlock(&flash_lock);
611da177e4SLinus Torvalds 			return -ENXIO;
621da177e4SLinus Torvalds 		}
631da177e4SLinus Torvalds 	}
641da177e4SLinus Torvalds 	spin_unlock(&flash_lock);
651da177e4SLinus Torvalds 
661da177e4SLinus Torvalds 	if ((vma->vm_pgoff << PAGE_SHIFT) > size)
671da177e4SLinus Torvalds 		return -ENXIO;
681da177e4SLinus Torvalds 	addr = vma->vm_pgoff + (addr >> PAGE_SHIFT);
691da177e4SLinus Torvalds 
701da177e4SLinus Torvalds 	if (vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT)) > size)
711da177e4SLinus Torvalds 		size = vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT));
721da177e4SLinus Torvalds 
7314778d90SDavid S. Miller 	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
741da177e4SLinus Torvalds 
751da177e4SLinus Torvalds 	if (io_remap_pfn_range(vma, vma->vm_start, addr, size, vma->vm_page_prot))
761da177e4SLinus Torvalds 		return -EAGAIN;
771da177e4SLinus Torvalds 
781da177e4SLinus Torvalds 	return 0;
791da177e4SLinus Torvalds }
801da177e4SLinus Torvalds 
811da177e4SLinus Torvalds static long long
821da177e4SLinus Torvalds flash_llseek(struct file *file, long long offset, int origin)
831da177e4SLinus Torvalds {
841da177e4SLinus Torvalds 	lock_kernel();
851da177e4SLinus Torvalds 	switch (origin) {
861da177e4SLinus Torvalds 		case 0:
871da177e4SLinus Torvalds 			file->f_pos = offset;
881da177e4SLinus Torvalds 			break;
891da177e4SLinus Torvalds 		case 1:
901da177e4SLinus Torvalds 			file->f_pos += offset;
911da177e4SLinus Torvalds 			if (file->f_pos > flash.read_size)
921da177e4SLinus Torvalds 				file->f_pos = flash.read_size;
931da177e4SLinus Torvalds 			break;
941da177e4SLinus Torvalds 		case 2:
951da177e4SLinus Torvalds 			file->f_pos = flash.read_size;
961da177e4SLinus Torvalds 			break;
971da177e4SLinus Torvalds 		default:
981da177e4SLinus Torvalds 			unlock_kernel();
991da177e4SLinus Torvalds 			return -EINVAL;
1001da177e4SLinus Torvalds 	}
1011da177e4SLinus Torvalds 	unlock_kernel();
1021da177e4SLinus Torvalds 	return file->f_pos;
1031da177e4SLinus Torvalds }
1041da177e4SLinus Torvalds 
1051da177e4SLinus Torvalds static ssize_t
1061da177e4SLinus Torvalds flash_read(struct file * file, char __user * buf,
1071da177e4SLinus Torvalds 	   size_t count, loff_t *ppos)
1081da177e4SLinus Torvalds {
1091da177e4SLinus Torvalds 	unsigned long p = file->f_pos;
1101da177e4SLinus Torvalds 	int i;
1111da177e4SLinus Torvalds 
1121da177e4SLinus Torvalds 	if (count > flash.read_size - p)
1131da177e4SLinus Torvalds 		count = flash.read_size - p;
1141da177e4SLinus Torvalds 
1151da177e4SLinus Torvalds 	for (i = 0; i < count; i++) {
1161da177e4SLinus Torvalds 		u8 data = upa_readb(flash.read_base + p + i);
1171da177e4SLinus Torvalds 		if (put_user(data, buf))
1181da177e4SLinus Torvalds 			return -EFAULT;
1191da177e4SLinus Torvalds 		buf++;
1201da177e4SLinus Torvalds 	}
1211da177e4SLinus Torvalds 
1221da177e4SLinus Torvalds 	file->f_pos += count;
1231da177e4SLinus Torvalds 	return count;
1241da177e4SLinus Torvalds }
1251da177e4SLinus Torvalds 
1261da177e4SLinus Torvalds static int
1271da177e4SLinus Torvalds flash_open(struct inode *inode, struct file *file)
1281da177e4SLinus Torvalds {
12978abb6acSArnd Bergmann 	lock_kernel();
13078abb6acSArnd Bergmann 	if (test_and_set_bit(0, (void *)&flash.busy) != 0) {
13178abb6acSArnd Bergmann 		unlock_kernel();
1321da177e4SLinus Torvalds 		return -EBUSY;
13378abb6acSArnd Bergmann 	}
1341da177e4SLinus Torvalds 
13578abb6acSArnd Bergmann 	unlock_kernel();
1361da177e4SLinus Torvalds 	return 0;
1371da177e4SLinus Torvalds }
1381da177e4SLinus Torvalds 
1391da177e4SLinus Torvalds static int
1401da177e4SLinus Torvalds flash_release(struct inode *inode, struct file *file)
1411da177e4SLinus Torvalds {
1421da177e4SLinus Torvalds 	spin_lock(&flash_lock);
1431da177e4SLinus Torvalds 	flash.busy = 0;
1441da177e4SLinus Torvalds 	spin_unlock(&flash_lock);
1451da177e4SLinus Torvalds 
1461da177e4SLinus Torvalds 	return 0;
1471da177e4SLinus Torvalds }
1481da177e4SLinus Torvalds 
14900977a59SArjan van de Ven static const struct file_operations flash_fops = {
1501da177e4SLinus Torvalds 	/* no write to the Flash, use mmap
1511da177e4SLinus Torvalds 	 * and play flash dependent tricks.
1521da177e4SLinus Torvalds 	 */
1531da177e4SLinus Torvalds 	.owner =	THIS_MODULE,
1541da177e4SLinus Torvalds 	.llseek =	flash_llseek,
1551da177e4SLinus Torvalds 	.read =		flash_read,
1561da177e4SLinus Torvalds 	.mmap =		flash_mmap,
1571da177e4SLinus Torvalds 	.open =		flash_open,
1581da177e4SLinus Torvalds 	.release =	flash_release,
1591da177e4SLinus Torvalds };
1601da177e4SLinus Torvalds 
1611da177e4SLinus Torvalds static struct miscdevice flash_dev = { FLASH_MINOR, "flash", &flash_fops };
1621da177e4SLinus Torvalds 
163a9540d34SDavid S. Miller static int __devinit flash_probe(struct of_device *op,
164a9540d34SDavid S. Miller 				 const struct of_device_id *match)
1651da177e4SLinus Torvalds {
166a9540d34SDavid S. Miller 	struct device_node *dp = op->node;
167a9540d34SDavid S. Miller 	struct device_node *parent;
1681da177e4SLinus Torvalds 
169a9540d34SDavid S. Miller 	parent = dp->parent;
170a9540d34SDavid S. Miller 
171a9540d34SDavid S. Miller 	if (strcmp(parent->name, "sbus") &&
172a9540d34SDavid S. Miller 	    strcmp(parent->name, "sbi") &&
173a9540d34SDavid S. Miller 	    strcmp(parent->name, "ebus"))
174a9540d34SDavid S. Miller 		return -ENODEV;
175a9540d34SDavid S. Miller 
176a9540d34SDavid S. Miller 	flash.read_base = op->resource[0].start;
177a9540d34SDavid S. Miller 	flash.read_size = resource_size(&op->resource[0]);
178a9540d34SDavid S. Miller 	if (op->resource[1].flags) {
179a9540d34SDavid S. Miller 		flash.write_base = op->resource[1].start;
180a9540d34SDavid S. Miller 		flash.write_size = resource_size(&op->resource[1]);
1811da177e4SLinus Torvalds 	} else {
182a9540d34SDavid S. Miller 		flash.write_base = op->resource[0].start;
183a9540d34SDavid S. Miller 		flash.write_size = resource_size(&op->resource[0]);
1841da177e4SLinus Torvalds 	}
1851da177e4SLinus Torvalds 	flash.busy = 0;
186690c8fd3SDavid S. Miller 
187a9540d34SDavid S. Miller 	printk(KERN_INFO "%s: OBP Flash, RD %lx[%lx] WR %lx[%lx]\n",
188a9540d34SDavid S. Miller 	       op->node->full_name,
1891da177e4SLinus Torvalds 	       flash.read_base, flash.read_size,
1901da177e4SLinus Torvalds 	       flash.write_base, flash.write_size);
1911da177e4SLinus Torvalds 
192a9540d34SDavid S. Miller 	return misc_register(&flash_dev);
1931da177e4SLinus Torvalds }
1941da177e4SLinus Torvalds 
195a9540d34SDavid S. Miller static int __devexit flash_remove(struct of_device *op)
196a9540d34SDavid S. Miller {
197a9540d34SDavid S. Miller 	misc_deregister(&flash_dev);
198a9540d34SDavid S. Miller 
1991da177e4SLinus Torvalds 	return 0;
2001da177e4SLinus Torvalds }
2011da177e4SLinus Torvalds 
202fd098316SDavid S. Miller static const struct of_device_id flash_match[] = {
203a9540d34SDavid S. Miller 	{
204a9540d34SDavid S. Miller 		.name = "flashprom",
205a9540d34SDavid S. Miller 	},
206a9540d34SDavid S. Miller 	{},
207a9540d34SDavid S. Miller };
208a9540d34SDavid S. Miller MODULE_DEVICE_TABLE(of, flash_match);
209a9540d34SDavid S. Miller 
210a9540d34SDavid S. Miller static struct of_platform_driver flash_driver = {
211a9540d34SDavid S. Miller 	.name		= "flash",
212a9540d34SDavid S. Miller 	.match_table	= flash_match,
213a9540d34SDavid S. Miller 	.probe		= flash_probe,
214a9540d34SDavid S. Miller 	.remove		= __devexit_p(flash_remove),
215a9540d34SDavid S. Miller };
216a9540d34SDavid S. Miller 
217a9540d34SDavid S. Miller static int __init flash_init(void)
218a9540d34SDavid S. Miller {
219a9540d34SDavid S. Miller 	return of_register_driver(&flash_driver, &of_bus_type);
220a9540d34SDavid S. Miller }
221a9540d34SDavid S. Miller 
2221da177e4SLinus Torvalds static void __exit flash_cleanup(void)
2231da177e4SLinus Torvalds {
224a9540d34SDavid S. Miller 	of_unregister_driver(&flash_driver);
2251da177e4SLinus Torvalds }
2261da177e4SLinus Torvalds 
2271da177e4SLinus Torvalds module_init(flash_init);
2281da177e4SLinus Torvalds module_exit(flash_cleanup);
2291da177e4SLinus Torvalds MODULE_LICENSE("GPL");
230