219 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			219 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* flash.c: Allow mmap access to the OBP Flash, for OBP updates.
 | 
						|
 *
 | 
						|
 * Copyright (C) 1997  Eddie C. Dost  (ecd@skynet.be)
 | 
						|
 */
 | 
						|
 | 
						|
#include <linux/module.h>
 | 
						|
#include <linux/types.h>
 | 
						|
#include <linux/errno.h>
 | 
						|
#include <linux/miscdevice.h>
 | 
						|
#include <linux/fcntl.h>
 | 
						|
#include <linux/poll.h>
 | 
						|
#include <linux/mutex.h>
 | 
						|
#include <linux/spinlock.h>
 | 
						|
#include <linux/mm.h>
 | 
						|
#include <linux/of.h>
 | 
						|
#include <linux/of_device.h>
 | 
						|
 | 
						|
#include <linux/uaccess.h>
 | 
						|
#include <asm/pgtable.h>
 | 
						|
#include <asm/io.h>
 | 
						|
#include <asm/upa.h>
 | 
						|
 | 
						|
static DEFINE_MUTEX(flash_mutex);
 | 
						|
static DEFINE_SPINLOCK(flash_lock);
 | 
						|
static struct {
 | 
						|
	unsigned long read_base;	/* Physical read address */
 | 
						|
	unsigned long write_base;	/* Physical write address */
 | 
						|
	unsigned long read_size;	/* Size of read area */
 | 
						|
	unsigned long write_size;	/* Size of write area */
 | 
						|
	unsigned long busy;		/* In use? */
 | 
						|
} flash;
 | 
						|
 | 
						|
#define FLASH_MINOR	152
 | 
						|
 | 
						|
static int
 | 
						|
flash_mmap(struct file *file, struct vm_area_struct *vma)
 | 
						|
{
 | 
						|
	unsigned long addr;
 | 
						|
	unsigned long size;
 | 
						|
 | 
						|
	spin_lock(&flash_lock);
 | 
						|
	if (flash.read_base == flash.write_base) {
 | 
						|
		addr = flash.read_base;
 | 
						|
		size = flash.read_size;
 | 
						|
	} else {
 | 
						|
		if ((vma->vm_flags & VM_READ) &&
 | 
						|
		    (vma->vm_flags & VM_WRITE)) {
 | 
						|
			spin_unlock(&flash_lock);
 | 
						|
			return -EINVAL;
 | 
						|
		}
 | 
						|
		if (vma->vm_flags & VM_READ) {
 | 
						|
			addr = flash.read_base;
 | 
						|
			size = flash.read_size;
 | 
						|
		} else if (vma->vm_flags & VM_WRITE) {
 | 
						|
			addr = flash.write_base;
 | 
						|
			size = flash.write_size;
 | 
						|
		} else {
 | 
						|
			spin_unlock(&flash_lock);
 | 
						|
			return -ENXIO;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	spin_unlock(&flash_lock);
 | 
						|
 | 
						|
	if ((vma->vm_pgoff << PAGE_SHIFT) > size)
 | 
						|
		return -ENXIO;
 | 
						|
	addr = vma->vm_pgoff + (addr >> PAGE_SHIFT);
 | 
						|
 | 
						|
	if (vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT)) > size)
 | 
						|
		size = vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT));
 | 
						|
 | 
						|
	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
 | 
						|
 | 
						|
	if (io_remap_pfn_range(vma, vma->vm_start, addr, size, vma->vm_page_prot))
 | 
						|
		return -EAGAIN;
 | 
						|
		
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static long long
 | 
						|
flash_llseek(struct file *file, long long offset, int origin)
 | 
						|
{
 | 
						|
	mutex_lock(&flash_mutex);
 | 
						|
	switch (origin) {
 | 
						|
		case 0:
 | 
						|
			file->f_pos = offset;
 | 
						|
			break;
 | 
						|
		case 1:
 | 
						|
			file->f_pos += offset;
 | 
						|
			if (file->f_pos > flash.read_size)
 | 
						|
				file->f_pos = flash.read_size;
 | 
						|
			break;
 | 
						|
		case 2:
 | 
						|
			file->f_pos = flash.read_size;
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			mutex_unlock(&flash_mutex);
 | 
						|
			return -EINVAL;
 | 
						|
	}
 | 
						|
	mutex_unlock(&flash_mutex);
 | 
						|
	return file->f_pos;
 | 
						|
}
 | 
						|
 | 
						|
static ssize_t
 | 
						|
flash_read(struct file * file, char __user * buf,
 | 
						|
	   size_t count, loff_t *ppos)
 | 
						|
{
 | 
						|
	loff_t p = *ppos;
 | 
						|
	int i;
 | 
						|
 | 
						|
	if (count > flash.read_size - p)
 | 
						|
		count = flash.read_size - p;
 | 
						|
 | 
						|
	for (i = 0; i < count; i++) {
 | 
						|
		u8 data = upa_readb(flash.read_base + p + i);
 | 
						|
		if (put_user(data, buf))
 | 
						|
			return -EFAULT;
 | 
						|
		buf++;
 | 
						|
	}
 | 
						|
 | 
						|
	*ppos += count;
 | 
						|
	return count;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
flash_open(struct inode *inode, struct file *file)
 | 
						|
{
 | 
						|
	mutex_lock(&flash_mutex);
 | 
						|
	if (test_and_set_bit(0, (void *)&flash.busy) != 0) {
 | 
						|
		mutex_unlock(&flash_mutex);
 | 
						|
		return -EBUSY;
 | 
						|
	}
 | 
						|
 | 
						|
	mutex_unlock(&flash_mutex);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
flash_release(struct inode *inode, struct file *file)
 | 
						|
{
 | 
						|
	spin_lock(&flash_lock);
 | 
						|
	flash.busy = 0;
 | 
						|
	spin_unlock(&flash_lock);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static const struct file_operations flash_fops = {
 | 
						|
	/* no write to the Flash, use mmap
 | 
						|
	 * and play flash dependent tricks.
 | 
						|
	 */
 | 
						|
	.owner =	THIS_MODULE,
 | 
						|
	.llseek =	flash_llseek,
 | 
						|
	.read =		flash_read,
 | 
						|
	.mmap =		flash_mmap,
 | 
						|
	.open =		flash_open,
 | 
						|
	.release =	flash_release,
 | 
						|
};
 | 
						|
 | 
						|
static struct miscdevice flash_dev = { FLASH_MINOR, "flash", &flash_fops };
 | 
						|
 | 
						|
static int flash_probe(struct platform_device *op)
 | 
						|
{
 | 
						|
	struct device_node *dp = op->dev.of_node;
 | 
						|
	struct device_node *parent;
 | 
						|
 | 
						|
	parent = dp->parent;
 | 
						|
 | 
						|
	if (strcmp(parent->name, "sbus") &&
 | 
						|
	    strcmp(parent->name, "sbi") &&
 | 
						|
	    strcmp(parent->name, "ebus"))
 | 
						|
		return -ENODEV;
 | 
						|
 | 
						|
	flash.read_base = op->resource[0].start;
 | 
						|
	flash.read_size = resource_size(&op->resource[0]);
 | 
						|
	if (op->resource[1].flags) {
 | 
						|
		flash.write_base = op->resource[1].start;
 | 
						|
		flash.write_size = resource_size(&op->resource[1]);
 | 
						|
	} else {
 | 
						|
		flash.write_base = op->resource[0].start;
 | 
						|
		flash.write_size = resource_size(&op->resource[0]);
 | 
						|
	}
 | 
						|
	flash.busy = 0;
 | 
						|
 | 
						|
	printk(KERN_INFO "%pOF: OBP Flash, RD %lx[%lx] WR %lx[%lx]\n",
 | 
						|
	       op->dev.of_node,
 | 
						|
	       flash.read_base, flash.read_size,
 | 
						|
	       flash.write_base, flash.write_size);
 | 
						|
 | 
						|
	return misc_register(&flash_dev);
 | 
						|
}
 | 
						|
 | 
						|
static int flash_remove(struct platform_device *op)
 | 
						|
{
 | 
						|
	misc_deregister(&flash_dev);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static const struct of_device_id flash_match[] = {
 | 
						|
	{
 | 
						|
		.name = "flashprom",
 | 
						|
	},
 | 
						|
	{},
 | 
						|
};
 | 
						|
MODULE_DEVICE_TABLE(of, flash_match);
 | 
						|
 | 
						|
static struct platform_driver flash_driver = {
 | 
						|
	.driver = {
 | 
						|
		.name = "flash",
 | 
						|
		.of_match_table = flash_match,
 | 
						|
	},
 | 
						|
	.probe		= flash_probe,
 | 
						|
	.remove		= flash_remove,
 | 
						|
};
 | 
						|
 | 
						|
module_platform_driver(flash_driver);
 | 
						|
 | 
						|
MODULE_LICENSE("GPL");
 |