18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* flash.c: Allow mmap access to the OBP Flash, for OBP updates. 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be) 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/module.h> 88c2ecf20Sopenharmony_ci#include <linux/types.h> 98c2ecf20Sopenharmony_ci#include <linux/errno.h> 108c2ecf20Sopenharmony_ci#include <linux/miscdevice.h> 118c2ecf20Sopenharmony_ci#include <linux/fcntl.h> 128c2ecf20Sopenharmony_ci#include <linux/poll.h> 138c2ecf20Sopenharmony_ci#include <linux/mutex.h> 148c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 158c2ecf20Sopenharmony_ci#include <linux/mm.h> 168c2ecf20Sopenharmony_ci#include <linux/of.h> 178c2ecf20Sopenharmony_ci#include <linux/of_device.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 208c2ecf20Sopenharmony_ci#include <asm/io.h> 218c2ecf20Sopenharmony_ci#include <asm/upa.h> 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(flash_mutex); 248c2ecf20Sopenharmony_cistatic DEFINE_SPINLOCK(flash_lock); 258c2ecf20Sopenharmony_cistatic struct { 268c2ecf20Sopenharmony_ci unsigned long read_base; /* Physical read address */ 278c2ecf20Sopenharmony_ci unsigned long write_base; /* Physical write address */ 288c2ecf20Sopenharmony_ci unsigned long read_size; /* Size of read area */ 298c2ecf20Sopenharmony_ci unsigned long write_size; /* Size of write area */ 308c2ecf20Sopenharmony_ci unsigned long busy; /* In use? */ 318c2ecf20Sopenharmony_ci} flash; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_cistatic int 348c2ecf20Sopenharmony_ciflash_mmap(struct file *file, struct vm_area_struct *vma) 358c2ecf20Sopenharmony_ci{ 368c2ecf20Sopenharmony_ci unsigned long addr; 378c2ecf20Sopenharmony_ci unsigned long size; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci spin_lock(&flash_lock); 408c2ecf20Sopenharmony_ci if (flash.read_base == flash.write_base) { 418c2ecf20Sopenharmony_ci addr = flash.read_base; 428c2ecf20Sopenharmony_ci size = flash.read_size; 438c2ecf20Sopenharmony_ci } else { 448c2ecf20Sopenharmony_ci if ((vma->vm_flags & VM_READ) && 458c2ecf20Sopenharmony_ci (vma->vm_flags & VM_WRITE)) { 468c2ecf20Sopenharmony_ci spin_unlock(&flash_lock); 478c2ecf20Sopenharmony_ci return -EINVAL; 488c2ecf20Sopenharmony_ci } 498c2ecf20Sopenharmony_ci if (vma->vm_flags & VM_READ) { 508c2ecf20Sopenharmony_ci addr = flash.read_base; 518c2ecf20Sopenharmony_ci size = flash.read_size; 528c2ecf20Sopenharmony_ci } else if (vma->vm_flags & VM_WRITE) { 538c2ecf20Sopenharmony_ci addr = flash.write_base; 548c2ecf20Sopenharmony_ci size = flash.write_size; 558c2ecf20Sopenharmony_ci } else { 568c2ecf20Sopenharmony_ci spin_unlock(&flash_lock); 578c2ecf20Sopenharmony_ci return -ENXIO; 588c2ecf20Sopenharmony_ci } 598c2ecf20Sopenharmony_ci } 608c2ecf20Sopenharmony_ci spin_unlock(&flash_lock); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci if ((vma->vm_pgoff << PAGE_SHIFT) > size) 638c2ecf20Sopenharmony_ci return -ENXIO; 648c2ecf20Sopenharmony_ci addr = vma->vm_pgoff + (addr >> PAGE_SHIFT); 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci if (vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT)) > size) 678c2ecf20Sopenharmony_ci size = vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT)); 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci if (io_remap_pfn_range(vma, vma->vm_start, addr, size, vma->vm_page_prot)) 728c2ecf20Sopenharmony_ci return -EAGAIN; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci return 0; 758c2ecf20Sopenharmony_ci} 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_cistatic long long 788c2ecf20Sopenharmony_ciflash_llseek(struct file *file, long long offset, int origin) 798c2ecf20Sopenharmony_ci{ 808c2ecf20Sopenharmony_ci mutex_lock(&flash_mutex); 818c2ecf20Sopenharmony_ci switch (origin) { 828c2ecf20Sopenharmony_ci case 0: 838c2ecf20Sopenharmony_ci file->f_pos = offset; 848c2ecf20Sopenharmony_ci break; 858c2ecf20Sopenharmony_ci case 1: 868c2ecf20Sopenharmony_ci file->f_pos += offset; 878c2ecf20Sopenharmony_ci if (file->f_pos > flash.read_size) 888c2ecf20Sopenharmony_ci file->f_pos = flash.read_size; 898c2ecf20Sopenharmony_ci break; 908c2ecf20Sopenharmony_ci case 2: 918c2ecf20Sopenharmony_ci file->f_pos = flash.read_size; 928c2ecf20Sopenharmony_ci break; 938c2ecf20Sopenharmony_ci default: 948c2ecf20Sopenharmony_ci mutex_unlock(&flash_mutex); 958c2ecf20Sopenharmony_ci return -EINVAL; 968c2ecf20Sopenharmony_ci } 978c2ecf20Sopenharmony_ci mutex_unlock(&flash_mutex); 988c2ecf20Sopenharmony_ci return file->f_pos; 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_cistatic ssize_t 1028c2ecf20Sopenharmony_ciflash_read(struct file * file, char __user * buf, 1038c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 1048c2ecf20Sopenharmony_ci{ 1058c2ecf20Sopenharmony_ci loff_t p = *ppos; 1068c2ecf20Sopenharmony_ci int i; 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci if (count > flash.read_size - p) 1098c2ecf20Sopenharmony_ci count = flash.read_size - p; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci for (i = 0; i < count; i++) { 1128c2ecf20Sopenharmony_ci u8 data = upa_readb(flash.read_base + p + i); 1138c2ecf20Sopenharmony_ci if (put_user(data, buf)) 1148c2ecf20Sopenharmony_ci return -EFAULT; 1158c2ecf20Sopenharmony_ci buf++; 1168c2ecf20Sopenharmony_ci } 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci *ppos += count; 1198c2ecf20Sopenharmony_ci return count; 1208c2ecf20Sopenharmony_ci} 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_cistatic int 1238c2ecf20Sopenharmony_ciflash_open(struct inode *inode, struct file *file) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci mutex_lock(&flash_mutex); 1268c2ecf20Sopenharmony_ci if (test_and_set_bit(0, (void *)&flash.busy) != 0) { 1278c2ecf20Sopenharmony_ci mutex_unlock(&flash_mutex); 1288c2ecf20Sopenharmony_ci return -EBUSY; 1298c2ecf20Sopenharmony_ci } 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci mutex_unlock(&flash_mutex); 1328c2ecf20Sopenharmony_ci return 0; 1338c2ecf20Sopenharmony_ci} 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic int 1368c2ecf20Sopenharmony_ciflash_release(struct inode *inode, struct file *file) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci spin_lock(&flash_lock); 1398c2ecf20Sopenharmony_ci flash.busy = 0; 1408c2ecf20Sopenharmony_ci spin_unlock(&flash_lock); 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci return 0; 1438c2ecf20Sopenharmony_ci} 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_cistatic const struct file_operations flash_fops = { 1468c2ecf20Sopenharmony_ci /* no write to the Flash, use mmap 1478c2ecf20Sopenharmony_ci * and play flash dependent tricks. 1488c2ecf20Sopenharmony_ci */ 1498c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1508c2ecf20Sopenharmony_ci .llseek = flash_llseek, 1518c2ecf20Sopenharmony_ci .read = flash_read, 1528c2ecf20Sopenharmony_ci .mmap = flash_mmap, 1538c2ecf20Sopenharmony_ci .open = flash_open, 1548c2ecf20Sopenharmony_ci .release = flash_release, 1558c2ecf20Sopenharmony_ci}; 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic struct miscdevice flash_dev = { SBUS_FLASH_MINOR, "flash", &flash_fops }; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic int flash_probe(struct platform_device *op) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci struct device_node *dp = op->dev.of_node; 1628c2ecf20Sopenharmony_ci struct device_node *parent; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci parent = dp->parent; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci if (!of_node_name_eq(parent, "sbus") && 1678c2ecf20Sopenharmony_ci !of_node_name_eq(parent, "sbi") && 1688c2ecf20Sopenharmony_ci !of_node_name_eq(parent, "ebus")) 1698c2ecf20Sopenharmony_ci return -ENODEV; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci flash.read_base = op->resource[0].start; 1728c2ecf20Sopenharmony_ci flash.read_size = resource_size(&op->resource[0]); 1738c2ecf20Sopenharmony_ci if (op->resource[1].flags) { 1748c2ecf20Sopenharmony_ci flash.write_base = op->resource[1].start; 1758c2ecf20Sopenharmony_ci flash.write_size = resource_size(&op->resource[1]); 1768c2ecf20Sopenharmony_ci } else { 1778c2ecf20Sopenharmony_ci flash.write_base = op->resource[0].start; 1788c2ecf20Sopenharmony_ci flash.write_size = resource_size(&op->resource[0]); 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci flash.busy = 0; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci printk(KERN_INFO "%pOF: OBP Flash, RD %lx[%lx] WR %lx[%lx]\n", 1838c2ecf20Sopenharmony_ci op->dev.of_node, 1848c2ecf20Sopenharmony_ci flash.read_base, flash.read_size, 1858c2ecf20Sopenharmony_ci flash.write_base, flash.write_size); 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci return misc_register(&flash_dev); 1888c2ecf20Sopenharmony_ci} 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cistatic int flash_remove(struct platform_device *op) 1918c2ecf20Sopenharmony_ci{ 1928c2ecf20Sopenharmony_ci misc_deregister(&flash_dev); 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci return 0; 1958c2ecf20Sopenharmony_ci} 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_cistatic const struct of_device_id flash_match[] = { 1988c2ecf20Sopenharmony_ci { 1998c2ecf20Sopenharmony_ci .name = "flashprom", 2008c2ecf20Sopenharmony_ci }, 2018c2ecf20Sopenharmony_ci {}, 2028c2ecf20Sopenharmony_ci}; 2038c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, flash_match); 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic struct platform_driver flash_driver = { 2068c2ecf20Sopenharmony_ci .driver = { 2078c2ecf20Sopenharmony_ci .name = "flash", 2088c2ecf20Sopenharmony_ci .of_match_table = flash_match, 2098c2ecf20Sopenharmony_ci }, 2108c2ecf20Sopenharmony_ci .probe = flash_probe, 2118c2ecf20Sopenharmony_ci .remove = flash_remove, 2128c2ecf20Sopenharmony_ci}; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_cimodule_platform_driver(flash_driver); 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 217