flash.c 4.7 KB
Newer Older
1
/* flash.c: Allow mmap access to the OBP Flash, for OBP updates.
L
Linus Torvalds 已提交
2 3 4 5 6 7 8 9 10 11
 *
 * 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>
12
#include <linux/mutex.h>
L
Linus Torvalds 已提交
13
#include <linux/spinlock.h>
14
#include <linux/mm.h>
15 16
#include <linux/of.h>
#include <linux/of_device.h>
L
Linus Torvalds 已提交
17

18
#include <linux/uaccess.h>
L
Linus Torvalds 已提交
19 20 21 22
#include <asm/pgtable.h>
#include <asm/io.h>
#include <asm/upa.h>

23
static DEFINE_MUTEX(flash_mutex);
L
Linus Torvalds 已提交
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
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));

71
	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
L
Linus Torvalds 已提交
72 73 74 75 76 77 78 79 80 81

	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)
{
82
	mutex_lock(&flash_mutex);
L
Linus Torvalds 已提交
83 84 85 86 87 88 89 90 91 92 93 94 95
	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:
96
			mutex_unlock(&flash_mutex);
L
Linus Torvalds 已提交
97 98
			return -EINVAL;
	}
99
	mutex_unlock(&flash_mutex);
L
Linus Torvalds 已提交
100 101 102 103 104 105 106
	return file->f_pos;
}

static ssize_t
flash_read(struct file * file, char __user * buf,
	   size_t count, loff_t *ppos)
{
107
	loff_t p = *ppos;
L
Linus Torvalds 已提交
108
	int i;
109

L
Linus Torvalds 已提交
110 111 112 113 114 115 116 117 118 119
	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++;
	}

120
	*ppos += count;
L
Linus Torvalds 已提交
121 122 123 124 125 126
	return count;
}

static int
flash_open(struct inode *inode, struct file *file)
{
127
	mutex_lock(&flash_mutex);
A
Arnd Bergmann 已提交
128
	if (test_and_set_bit(0, (void *)&flash.busy) != 0) {
129
		mutex_unlock(&flash_mutex);
L
Linus Torvalds 已提交
130
		return -EBUSY;
A
Arnd Bergmann 已提交
131
	}
L
Linus Torvalds 已提交
132

133
	mutex_unlock(&flash_mutex);
L
Linus Torvalds 已提交
134 135 136 137 138 139 140 141 142 143 144 145 146
	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;
}

147
static const struct file_operations flash_fops = {
L
Linus Torvalds 已提交
148 149 150 151 152 153 154 155 156 157 158 159 160
	/* 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 };

161
static int flash_probe(struct platform_device *op)
L
Linus Torvalds 已提交
162
{
163
	struct device_node *dp = op->dev.of_node;
164
	struct device_node *parent;
L
Linus Torvalds 已提交
165

166
	parent = dp->parent;
L
Linus Torvalds 已提交
167

168 169 170
	if (strcmp(parent->name, "sbus") &&
	    strcmp(parent->name, "sbi") &&
	    strcmp(parent->name, "ebus"))
L
Linus Torvalds 已提交
171
		return -ENODEV;
172 173 174 175 176 177 178 179 180

	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]);
L
Linus Torvalds 已提交
181
	}
182
	flash.busy = 0;
L
Linus Torvalds 已提交
183

184 185
	printk(KERN_INFO "%pOF: OBP Flash, RD %lx[%lx] WR %lx[%lx]\n",
	       op->dev.of_node,
L
Linus Torvalds 已提交
186 187 188
	       flash.read_base, flash.read_size,
	       flash.write_base, flash.write_size);

189 190 191
	return misc_register(&flash_dev);
}

192
static int flash_remove(struct platform_device *op)
193 194
{
	misc_deregister(&flash_dev);
L
Linus Torvalds 已提交
195 196 197 198

	return 0;
}

199
static const struct of_device_id flash_match[] = {
200 201 202 203 204 205 206
	{
		.name = "flashprom",
	},
	{},
};
MODULE_DEVICE_TABLE(of, flash_match);

207
static struct platform_driver flash_driver = {
208 209 210 211
	.driver = {
		.name = "flash",
		.of_match_table = flash_match,
	},
212
	.probe		= flash_probe,
213
	.remove		= flash_remove,
214 215
};

216
module_platform_driver(flash_driver);
L
Linus Torvalds 已提交
217 218

MODULE_LICENSE("GPL");