Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

138
drivers/uio/Kconfig Normal file
View file

@ -0,0 +1,138 @@
menuconfig UIO
tristate "Userspace I/O drivers"
depends on MMU
help
Enable this to allow the userspace driver core code to be
built. This code allows userspace programs easy access to
kernel interrupts and memory locations, allowing some drivers
to be written in userspace. Note that a small kernel driver
is also required for interrupt handling to work properly.
If you don't know what to do here, say N.
if UIO
config UIO_CIF
tristate "generic Hilscher CIF Card driver"
depends on PCI
help
Driver for Hilscher CIF DeviceNet and Profibus cards. This
driver requires a userspace component called cif that handles
all of the heavy lifting and can be found at:
<http://www.osadl.org/projects/downloads/UIO/user/>
To compile this driver as a module, choose M here: the module
will be called uio_cif.
config UIO_PDRV_GENIRQ
tristate "Userspace I/O platform driver with generic IRQ handling"
help
Platform driver for Userspace I/O devices, including generic
interrupt handling code. Shared interrupts are not supported.
This kernel driver requires that the matching userspace driver
handles interrupts in a special way. Userspace is responsible
for acknowledging the hardware device if needed, and re-enabling
interrupts in the interrupt controller using the write() syscall.
If you don't know what to do here, say N.
config UIO_DMEM_GENIRQ
tristate "Userspace platform driver with generic irq and dynamic memory"
depends on HAS_DMA
help
Platform driver for Userspace I/O devices, including generic
interrupt handling code. Shared interrupts are not supported.
Memory regions can be specified with the same platform device
resources as the UIO_PDRV drivers, but dynamic regions can also
be specified.
The number and size of these regions is static,
but the memory allocation is not performed until
the associated device file is opened. The
memory is freed once the uio device is closed.
If you don't know what to do here, say N.
config UIO_AEC
tristate "AEC video timestamp device"
depends on PCI
help
UIO driver for the Adrienne Electronics Corporation PCI time
code device.
This device differs from other UIO devices since it uses I/O
ports instead of memory mapped I/O. In order to make it
possible for UIO to work with this device a utility, uioport,
can be used to read and write the ports:
git clone git://ifup.org/philips/uioport.git
If you compile this as a module, it will be called uio_aec.
config UIO_SERCOS3
tristate "Automata Sercos III PCI card driver"
depends on PCI
help
Userspace I/O interface for the Sercos III PCI card from
Automata GmbH. The userspace part of this driver will be
available for download from the Automata GmbH web site.
Automata GmbH: http://www.automataweb.com
Sercos III interface: http://www.sercos.com
If you compile this as a module, it will be called uio_sercos3.
config UIO_PCI_GENERIC
tristate "Generic driver for PCI 2.3 and PCI Express cards"
depends on PCI
help
Generic driver that you can bind, dynamically, to any
PCI 2.3 compliant and PCI Express card. It is useful,
primarily, for virtualization scenarios.
If you compile this as a module, it will be called uio_pci_generic.
config UIO_NETX
tristate "Hilscher NetX Card driver"
depends on PCI
help
Driver for Hilscher NetX based fieldbus cards (cifX, comX).
This driver requires a userspace component that comes with the card
or is available from Hilscher (http://www.hilscher.com).
To compile this driver as a module, choose M here; the module
will be called uio_netx.
config UIO_PRUSS
tristate "Texas Instruments PRUSS driver"
depends on ARCH_DAVINCI_DA850
select GENERIC_ALLOCATOR
help
PRUSS driver for OMAPL138/DA850/AM18XX devices
PRUSS driver requires user space components, examples and user space
driver is available from below SVN repo - you may use anonymous login
https://gforge.ti.com/gf/project/pru_sw/
More info on API is available at below wiki
http://processors.wiki.ti.com/index.php/PRU_Linux_Application_Loader
To compile this driver as a module, choose M here: the module
will be called uio_pruss.
config UIO_MF624
tristate "Humusoft MF624 DAQ PCI card driver"
depends on PCI
help
Userspace I/O interface for the Humusoft MF624 PCI card.
A sample userspace application using this driver is available
(among other MF624 related information and software components)
for download in a git repository:
git clone git://rtime.felk.cvut.cz/mf6xx.git
If you compile this as a module, it will be called uio_mf624.
endif

10
drivers/uio/Makefile Normal file
View file

@ -0,0 +1,10 @@
obj-$(CONFIG_UIO) += uio.o
obj-$(CONFIG_UIO_CIF) += uio_cif.o
obj-$(CONFIG_UIO_PDRV_GENIRQ) += uio_pdrv_genirq.o
obj-$(CONFIG_UIO_DMEM_GENIRQ) += uio_dmem_genirq.o
obj-$(CONFIG_UIO_AEC) += uio_aec.o
obj-$(CONFIG_UIO_SERCOS3) += uio_sercos3.o
obj-$(CONFIG_UIO_PCI_GENERIC) += uio_pci_generic.o
obj-$(CONFIG_UIO_NETX) += uio_netx.o
obj-$(CONFIG_UIO_PRUSS) += uio_pruss.o
obj-$(CONFIG_UIO_MF624) += uio_mf624.o

892
drivers/uio/uio.c Normal file
View file

@ -0,0 +1,892 @@
/*
* drivers/uio/uio.c
*
* Copyright(C) 2005, Benedikt Spranger <b.spranger@linutronix.de>
* Copyright(C) 2005, Thomas Gleixner <tglx@linutronix.de>
* Copyright(C) 2006, Hans J. Koch <hjk@hansjkoch.de>
* Copyright(C) 2006, Greg Kroah-Hartman <greg@kroah.com>
*
* Userspace IO
*
* Base Functions
*
* Licensed under the GPLv2 only.
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/idr.h>
#include <linux/sched.h>
#include <linux/string.h>
#include <linux/kobject.h>
#include <linux/cdev.h>
#include <linux/uio_driver.h>
#define UIO_MAX_DEVICES (1U << MINORBITS)
static int uio_major;
static struct cdev *uio_cdev;
static DEFINE_IDR(uio_idr);
static const struct file_operations uio_fops;
/* Protect idr accesses */
static DEFINE_MUTEX(minor_lock);
/*
* attributes
*/
struct uio_map {
struct kobject kobj;
struct uio_mem *mem;
};
#define to_map(map) container_of(map, struct uio_map, kobj)
static ssize_t map_name_show(struct uio_mem *mem, char *buf)
{
if (unlikely(!mem->name))
mem->name = "";
return sprintf(buf, "%s\n", mem->name);
}
static ssize_t map_addr_show(struct uio_mem *mem, char *buf)
{
return sprintf(buf, "0x%llx\n", (unsigned long long)mem->addr);
}
static ssize_t map_size_show(struct uio_mem *mem, char *buf)
{
return sprintf(buf, "0x%lx\n", mem->size);
}
static ssize_t map_offset_show(struct uio_mem *mem, char *buf)
{
return sprintf(buf, "0x%llx\n", (unsigned long long)mem->addr & ~PAGE_MASK);
}
struct map_sysfs_entry {
struct attribute attr;
ssize_t (*show)(struct uio_mem *, char *);
ssize_t (*store)(struct uio_mem *, const char *, size_t);
};
static struct map_sysfs_entry name_attribute =
__ATTR(name, S_IRUGO, map_name_show, NULL);
static struct map_sysfs_entry addr_attribute =
__ATTR(addr, S_IRUGO, map_addr_show, NULL);
static struct map_sysfs_entry size_attribute =
__ATTR(size, S_IRUGO, map_size_show, NULL);
static struct map_sysfs_entry offset_attribute =
__ATTR(offset, S_IRUGO, map_offset_show, NULL);
static struct attribute *attrs[] = {
&name_attribute.attr,
&addr_attribute.attr,
&size_attribute.attr,
&offset_attribute.attr,
NULL, /* need to NULL terminate the list of attributes */
};
static void map_release(struct kobject *kobj)
{
struct uio_map *map = to_map(kobj);
kfree(map);
}
static ssize_t map_type_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct uio_map *map = to_map(kobj);
struct uio_mem *mem = map->mem;
struct map_sysfs_entry *entry;
entry = container_of(attr, struct map_sysfs_entry, attr);
if (!entry->show)
return -EIO;
return entry->show(mem, buf);
}
static const struct sysfs_ops map_sysfs_ops = {
.show = map_type_show,
};
static struct kobj_type map_attr_type = {
.release = map_release,
.sysfs_ops = &map_sysfs_ops,
.default_attrs = attrs,
};
struct uio_portio {
struct kobject kobj;
struct uio_port *port;
};
#define to_portio(portio) container_of(portio, struct uio_portio, kobj)
static ssize_t portio_name_show(struct uio_port *port, char *buf)
{
if (unlikely(!port->name))
port->name = "";
return sprintf(buf, "%s\n", port->name);
}
static ssize_t portio_start_show(struct uio_port *port, char *buf)
{
return sprintf(buf, "0x%lx\n", port->start);
}
static ssize_t portio_size_show(struct uio_port *port, char *buf)
{
return sprintf(buf, "0x%lx\n", port->size);
}
static ssize_t portio_porttype_show(struct uio_port *port, char *buf)
{
const char *porttypes[] = {"none", "x86", "gpio", "other"};
if ((port->porttype < 0) || (port->porttype > UIO_PORT_OTHER))
return -EINVAL;
return sprintf(buf, "port_%s\n", porttypes[port->porttype]);
}
struct portio_sysfs_entry {
struct attribute attr;
ssize_t (*show)(struct uio_port *, char *);
ssize_t (*store)(struct uio_port *, const char *, size_t);
};
static struct portio_sysfs_entry portio_name_attribute =
__ATTR(name, S_IRUGO, portio_name_show, NULL);
static struct portio_sysfs_entry portio_start_attribute =
__ATTR(start, S_IRUGO, portio_start_show, NULL);
static struct portio_sysfs_entry portio_size_attribute =
__ATTR(size, S_IRUGO, portio_size_show, NULL);
static struct portio_sysfs_entry portio_porttype_attribute =
__ATTR(porttype, S_IRUGO, portio_porttype_show, NULL);
static struct attribute *portio_attrs[] = {
&portio_name_attribute.attr,
&portio_start_attribute.attr,
&portio_size_attribute.attr,
&portio_porttype_attribute.attr,
NULL,
};
static void portio_release(struct kobject *kobj)
{
struct uio_portio *portio = to_portio(kobj);
kfree(portio);
}
static ssize_t portio_type_show(struct kobject *kobj, struct attribute *attr,
char *buf)
{
struct uio_portio *portio = to_portio(kobj);
struct uio_port *port = portio->port;
struct portio_sysfs_entry *entry;
entry = container_of(attr, struct portio_sysfs_entry, attr);
if (!entry->show)
return -EIO;
return entry->show(port, buf);
}
static const struct sysfs_ops portio_sysfs_ops = {
.show = portio_type_show,
};
static struct kobj_type portio_attr_type = {
.release = portio_release,
.sysfs_ops = &portio_sysfs_ops,
.default_attrs = portio_attrs,
};
static ssize_t name_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct uio_device *idev = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", idev->info->name);
}
static DEVICE_ATTR_RO(name);
static ssize_t version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct uio_device *idev = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", idev->info->version);
}
static DEVICE_ATTR_RO(version);
static ssize_t event_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct uio_device *idev = dev_get_drvdata(dev);
return sprintf(buf, "%u\n", (unsigned int)atomic_read(&idev->event));
}
static DEVICE_ATTR_RO(event);
static struct attribute *uio_attrs[] = {
&dev_attr_name.attr,
&dev_attr_version.attr,
&dev_attr_event.attr,
NULL,
};
ATTRIBUTE_GROUPS(uio);
/* UIO class infrastructure */
static struct class uio_class = {
.name = "uio",
.dev_groups = uio_groups,
};
/*
* device functions
*/
static int uio_dev_add_attributes(struct uio_device *idev)
{
int ret;
int mi, pi;
int map_found = 0;
int portio_found = 0;
struct uio_mem *mem;
struct uio_map *map;
struct uio_port *port;
struct uio_portio *portio;
for (mi = 0; mi < MAX_UIO_MAPS; mi++) {
mem = &idev->info->mem[mi];
if (mem->size == 0)
break;
if (!map_found) {
map_found = 1;
idev->map_dir = kobject_create_and_add("maps",
&idev->dev->kobj);
if (!idev->map_dir)
goto err_map;
}
map = kzalloc(sizeof(*map), GFP_KERNEL);
if (!map)
goto err_map_kobj;
kobject_init(&map->kobj, &map_attr_type);
map->mem = mem;
mem->map = map;
ret = kobject_add(&map->kobj, idev->map_dir, "map%d", mi);
if (ret)
goto err_map_kobj;
ret = kobject_uevent(&map->kobj, KOBJ_ADD);
if (ret)
goto err_map;
}
for (pi = 0; pi < MAX_UIO_PORT_REGIONS; pi++) {
port = &idev->info->port[pi];
if (port->size == 0)
break;
if (!portio_found) {
portio_found = 1;
idev->portio_dir = kobject_create_and_add("portio",
&idev->dev->kobj);
if (!idev->portio_dir)
goto err_portio;
}
portio = kzalloc(sizeof(*portio), GFP_KERNEL);
if (!portio)
goto err_portio_kobj;
kobject_init(&portio->kobj, &portio_attr_type);
portio->port = port;
port->portio = portio;
ret = kobject_add(&portio->kobj, idev->portio_dir,
"port%d", pi);
if (ret)
goto err_portio_kobj;
ret = kobject_uevent(&portio->kobj, KOBJ_ADD);
if (ret)
goto err_portio;
}
return 0;
err_portio:
pi--;
err_portio_kobj:
for (; pi >= 0; pi--) {
port = &idev->info->port[pi];
portio = port->portio;
kobject_put(&portio->kobj);
}
kobject_put(idev->portio_dir);
err_map:
mi--;
err_map_kobj:
for (; mi >= 0; mi--) {
mem = &idev->info->mem[mi];
map = mem->map;
kobject_put(&map->kobj);
}
kobject_put(idev->map_dir);
dev_err(idev->dev, "error creating sysfs files (%d)\n", ret);
return ret;
}
static void uio_dev_del_attributes(struct uio_device *idev)
{
int i;
struct uio_mem *mem;
struct uio_port *port;
for (i = 0; i < MAX_UIO_MAPS; i++) {
mem = &idev->info->mem[i];
if (mem->size == 0)
break;
kobject_put(&mem->map->kobj);
}
kobject_put(idev->map_dir);
for (i = 0; i < MAX_UIO_PORT_REGIONS; i++) {
port = &idev->info->port[i];
if (port->size == 0)
break;
kobject_put(&port->portio->kobj);
}
kobject_put(idev->portio_dir);
}
static int uio_get_minor(struct uio_device *idev)
{
int retval = -ENOMEM;
mutex_lock(&minor_lock);
retval = idr_alloc(&uio_idr, idev, 0, UIO_MAX_DEVICES, GFP_KERNEL);
if (retval >= 0) {
idev->minor = retval;
retval = 0;
} else if (retval == -ENOSPC) {
dev_err(idev->dev, "too many uio devices\n");
retval = -EINVAL;
}
mutex_unlock(&minor_lock);
return retval;
}
static void uio_free_minor(struct uio_device *idev)
{
mutex_lock(&minor_lock);
idr_remove(&uio_idr, idev->minor);
mutex_unlock(&minor_lock);
}
/**
* uio_event_notify - trigger an interrupt event
* @info: UIO device capabilities
*/
void uio_event_notify(struct uio_info *info)
{
struct uio_device *idev = info->uio_dev;
atomic_inc(&idev->event);
wake_up_interruptible(&idev->wait);
kill_fasync(&idev->async_queue, SIGIO, POLL_IN);
}
EXPORT_SYMBOL_GPL(uio_event_notify);
/**
* uio_interrupt - hardware interrupt handler
* @irq: IRQ number, can be UIO_IRQ_CYCLIC for cyclic timer
* @dev_id: Pointer to the devices uio_device structure
*/
static irqreturn_t uio_interrupt(int irq, void *dev_id)
{
struct uio_device *idev = (struct uio_device *)dev_id;
irqreturn_t ret = idev->info->handler(irq, idev->info);
if (ret == IRQ_HANDLED)
uio_event_notify(idev->info);
return ret;
}
struct uio_listener {
struct uio_device *dev;
s32 event_count;
};
static int uio_open(struct inode *inode, struct file *filep)
{
struct uio_device *idev;
struct uio_listener *listener;
int ret = 0;
mutex_lock(&minor_lock);
idev = idr_find(&uio_idr, iminor(inode));
mutex_unlock(&minor_lock);
if (!idev) {
ret = -ENODEV;
goto out;
}
if (!try_module_get(idev->owner)) {
ret = -ENODEV;
goto out;
}
listener = kmalloc(sizeof(*listener), GFP_KERNEL);
if (!listener) {
ret = -ENOMEM;
goto err_alloc_listener;
}
listener->dev = idev;
listener->event_count = atomic_read(&idev->event);
filep->private_data = listener;
if (idev->info->open) {
ret = idev->info->open(idev->info, inode);
if (ret)
goto err_infoopen;
}
return 0;
err_infoopen:
kfree(listener);
err_alloc_listener:
module_put(idev->owner);
out:
return ret;
}
static int uio_fasync(int fd, struct file *filep, int on)
{
struct uio_listener *listener = filep->private_data;
struct uio_device *idev = listener->dev;
return fasync_helper(fd, filep, on, &idev->async_queue);
}
static int uio_release(struct inode *inode, struct file *filep)
{
int ret = 0;
struct uio_listener *listener = filep->private_data;
struct uio_device *idev = listener->dev;
if (idev->info->release)
ret = idev->info->release(idev->info, inode);
module_put(idev->owner);
kfree(listener);
return ret;
}
static unsigned int uio_poll(struct file *filep, poll_table *wait)
{
struct uio_listener *listener = filep->private_data;
struct uio_device *idev = listener->dev;
if (!idev->info->irq)
return -EIO;
poll_wait(filep, &idev->wait, wait);
if (listener->event_count != atomic_read(&idev->event))
return POLLIN | POLLRDNORM;
return 0;
}
static ssize_t uio_read(struct file *filep, char __user *buf,
size_t count, loff_t *ppos)
{
struct uio_listener *listener = filep->private_data;
struct uio_device *idev = listener->dev;
DECLARE_WAITQUEUE(wait, current);
ssize_t retval;
s32 event_count;
if (!idev->info->irq)
return -EIO;
if (count != sizeof(s32))
return -EINVAL;
add_wait_queue(&idev->wait, &wait);
do {
set_current_state(TASK_INTERRUPTIBLE);
event_count = atomic_read(&idev->event);
if (event_count != listener->event_count) {
if (copy_to_user(buf, &event_count, count))
retval = -EFAULT;
else {
listener->event_count = event_count;
retval = count;
}
break;
}
if (filep->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
schedule();
} while (1);
__set_current_state(TASK_RUNNING);
remove_wait_queue(&idev->wait, &wait);
return retval;
}
static ssize_t uio_write(struct file *filep, const char __user *buf,
size_t count, loff_t *ppos)
{
struct uio_listener *listener = filep->private_data;
struct uio_device *idev = listener->dev;
ssize_t retval;
s32 irq_on;
if (!idev->info->irq)
return -EIO;
if (count != sizeof(s32))
return -EINVAL;
if (!idev->info->irqcontrol)
return -ENOSYS;
if (copy_from_user(&irq_on, buf, count))
return -EFAULT;
retval = idev->info->irqcontrol(idev->info, irq_on);
return retval ? retval : sizeof(s32);
}
static int uio_find_mem_index(struct vm_area_struct *vma)
{
struct uio_device *idev = vma->vm_private_data;
if (vma->vm_pgoff < MAX_UIO_MAPS) {
if (idev->info->mem[vma->vm_pgoff].size == 0)
return -1;
return (int)vma->vm_pgoff;
}
return -1;
}
static int uio_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf)
{
struct uio_device *idev = vma->vm_private_data;
struct page *page;
unsigned long offset;
void *addr;
int mi = uio_find_mem_index(vma);
if (mi < 0)
return VM_FAULT_SIGBUS;
/*
* We need to subtract mi because userspace uses offset = N*PAGE_SIZE
* to use mem[N].
*/
offset = (vmf->pgoff - mi) << PAGE_SHIFT;
addr = (void *)(unsigned long)idev->info->mem[mi].addr + offset;
if (idev->info->mem[mi].memtype == UIO_MEM_LOGICAL)
page = virt_to_page(addr);
else
page = vmalloc_to_page(addr);
get_page(page);
vmf->page = page;
return 0;
}
static const struct vm_operations_struct uio_logical_vm_ops = {
.fault = uio_vma_fault,
};
static int uio_mmap_logical(struct vm_area_struct *vma)
{
vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
vma->vm_ops = &uio_logical_vm_ops;
return 0;
}
static const struct vm_operations_struct uio_physical_vm_ops = {
#ifdef CONFIG_HAVE_IOREMAP_PROT
.access = generic_access_phys,
#endif
};
static int uio_mmap_physical(struct vm_area_struct *vma)
{
struct uio_device *idev = vma->vm_private_data;
int mi = uio_find_mem_index(vma);
struct uio_mem *mem;
if (mi < 0)
return -EINVAL;
mem = idev->info->mem + mi;
if (mem->addr & ~PAGE_MASK)
return -ENODEV;
if (vma->vm_end - vma->vm_start > mem->size)
return -EINVAL;
vma->vm_ops = &uio_physical_vm_ops;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
/*
* We cannot use the vm_iomap_memory() helper here,
* because vma->vm_pgoff is the map index we looked
* up above in uio_find_mem_index(), rather than an
* actual page offset into the mmap.
*
* So we just do the physical mmap without a page
* offset.
*/
return remap_pfn_range(vma,
vma->vm_start,
mem->addr >> PAGE_SHIFT,
vma->vm_end - vma->vm_start,
vma->vm_page_prot);
}
static int uio_mmap(struct file *filep, struct vm_area_struct *vma)
{
struct uio_listener *listener = filep->private_data;
struct uio_device *idev = listener->dev;
int mi;
unsigned long requested_pages, actual_pages;
int ret = 0;
if (vma->vm_end < vma->vm_start)
return -EINVAL;
vma->vm_private_data = idev;
mi = uio_find_mem_index(vma);
if (mi < 0)
return -EINVAL;
requested_pages = vma_pages(vma);
actual_pages = ((idev->info->mem[mi].addr & ~PAGE_MASK)
+ idev->info->mem[mi].size + PAGE_SIZE -1) >> PAGE_SHIFT;
if (requested_pages > actual_pages)
return -EINVAL;
if (idev->info->mmap) {
ret = idev->info->mmap(idev->info, vma);
return ret;
}
switch (idev->info->mem[mi].memtype) {
case UIO_MEM_PHYS:
return uio_mmap_physical(vma);
case UIO_MEM_LOGICAL:
case UIO_MEM_VIRTUAL:
return uio_mmap_logical(vma);
default:
return -EINVAL;
}
}
static const struct file_operations uio_fops = {
.owner = THIS_MODULE,
.open = uio_open,
.release = uio_release,
.read = uio_read,
.write = uio_write,
.mmap = uio_mmap,
.poll = uio_poll,
.fasync = uio_fasync,
.llseek = noop_llseek,
};
static int uio_major_init(void)
{
static const char name[] = "uio";
struct cdev *cdev = NULL;
dev_t uio_dev = 0;
int result;
result = alloc_chrdev_region(&uio_dev, 0, UIO_MAX_DEVICES, name);
if (result)
goto out;
result = -ENOMEM;
cdev = cdev_alloc();
if (!cdev)
goto out_unregister;
cdev->owner = THIS_MODULE;
cdev->ops = &uio_fops;
kobject_set_name(&cdev->kobj, "%s", name);
result = cdev_add(cdev, uio_dev, UIO_MAX_DEVICES);
if (result)
goto out_put;
uio_major = MAJOR(uio_dev);
uio_cdev = cdev;
return 0;
out_put:
kobject_put(&cdev->kobj);
out_unregister:
unregister_chrdev_region(uio_dev, UIO_MAX_DEVICES);
out:
return result;
}
static void uio_major_cleanup(void)
{
unregister_chrdev_region(MKDEV(uio_major, 0), UIO_MAX_DEVICES);
cdev_del(uio_cdev);
}
static int init_uio_class(void)
{
int ret;
/* This is the first time in here, set everything up properly */
ret = uio_major_init();
if (ret)
goto exit;
ret = class_register(&uio_class);
if (ret) {
printk(KERN_ERR "class_register failed for uio\n");
goto err_class_register;
}
return 0;
err_class_register:
uio_major_cleanup();
exit:
return ret;
}
static void release_uio_class(void)
{
class_unregister(&uio_class);
uio_major_cleanup();
}
/**
* uio_register_device - register a new userspace IO device
* @owner: module that creates the new device
* @parent: parent device
* @info: UIO device capabilities
*
* returns zero on success or a negative error code.
*/
int __uio_register_device(struct module *owner,
struct device *parent,
struct uio_info *info)
{
struct uio_device *idev;
int ret = 0;
if (!parent || !info || !info->name || !info->version)
return -EINVAL;
info->uio_dev = NULL;
idev = devm_kzalloc(parent, sizeof(*idev), GFP_KERNEL);
if (!idev) {
return -ENOMEM;
}
idev->owner = owner;
idev->info = info;
init_waitqueue_head(&idev->wait);
atomic_set(&idev->event, 0);
ret = uio_get_minor(idev);
if (ret)
return ret;
idev->dev = device_create(&uio_class, parent,
MKDEV(uio_major, idev->minor), idev,
"uio%d", idev->minor);
if (IS_ERR(idev->dev)) {
printk(KERN_ERR "UIO: device register failed\n");
ret = PTR_ERR(idev->dev);
goto err_device_create;
}
ret = uio_dev_add_attributes(idev);
if (ret)
goto err_uio_dev_add_attributes;
info->uio_dev = idev;
if (info->irq && (info->irq != UIO_IRQ_CUSTOM)) {
ret = devm_request_irq(idev->dev, info->irq, uio_interrupt,
info->irq_flags, info->name, idev);
if (ret)
goto err_request_irq;
}
return 0;
err_request_irq:
uio_dev_del_attributes(idev);
err_uio_dev_add_attributes:
device_destroy(&uio_class, MKDEV(uio_major, idev->minor));
err_device_create:
uio_free_minor(idev);
return ret;
}
EXPORT_SYMBOL_GPL(__uio_register_device);
/**
* uio_unregister_device - unregister a industrial IO device
* @info: UIO device capabilities
*
*/
void uio_unregister_device(struct uio_info *info)
{
struct uio_device *idev;
if (!info || !info->uio_dev)
return;
idev = info->uio_dev;
uio_free_minor(idev);
uio_dev_del_attributes(idev);
device_destroy(&uio_class, MKDEV(uio_major, idev->minor));
return;
}
EXPORT_SYMBOL_GPL(uio_unregister_device);
static int __init uio_init(void)
{
return init_uio_class();
}
static void __exit uio_exit(void)
{
release_uio_class();
}
module_init(uio_init)
module_exit(uio_exit)
MODULE_LICENSE("GPL v2");

163
drivers/uio/uio_aec.c Normal file
View file

@ -0,0 +1,163 @@
/*
* uio_aec.c -- simple driver for Adrienne Electronics Corp time code PCI device
*
* Copyright (C) 2008 Brandon Philips <brandon@ifup.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation.
*
* 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.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/uio_driver.h>
#include <linux/slab.h>
#define PCI_VENDOR_ID_AEC 0xaecb
#define PCI_DEVICE_ID_AEC_VITCLTC 0x6250
#define INT_ENABLE_ADDR 0xFC
#define INT_ENABLE 0x10
#define INT_DISABLE 0x0
#define INT_MASK_ADDR 0x2E
#define INT_MASK_ALL 0x3F
#define INTA_DRVR_ADDR 0xFE
#define INTA_ENABLED_FLAG 0x08
#define INTA_FLAG 0x01
#define MAILBOX 0x0F
static struct pci_device_id ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AEC, PCI_DEVICE_ID_AEC_VITCLTC), },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, ids);
static irqreturn_t aectc_irq(int irq, struct uio_info *dev_info)
{
void __iomem *int_flag = dev_info->priv + INTA_DRVR_ADDR;
unsigned char status = ioread8(int_flag);
if ((status & INTA_ENABLED_FLAG) && (status & INTA_FLAG)) {
/* application writes 0x00 to 0x2F to get next interrupt */
status = ioread8(dev_info->priv + MAILBOX);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static void print_board_data(struct pci_dev *pdev, struct uio_info *i)
{
dev_info(&pdev->dev, "PCI-TC board vendor: %x%x number: %x%x"
" revision: %c%c\n",
ioread8(i->priv + 0x01),
ioread8(i->priv + 0x00),
ioread8(i->priv + 0x03),
ioread8(i->priv + 0x02),
ioread8(i->priv + 0x06),
ioread8(i->priv + 0x07));
}
static int probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct uio_info *info;
int ret;
info = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
if (pci_enable_device(pdev))
goto out_free;
if (pci_request_regions(pdev, "aectc"))
goto out_disable;
info->name = "aectc";
info->port[0].start = pci_resource_start(pdev, 0);
if (!info->port[0].start)
goto out_release;
info->priv = pci_iomap(pdev, 0, 0);
if (!info->priv)
goto out_release;
info->port[0].size = pci_resource_len(pdev, 0);
info->port[0].porttype = UIO_PORT_GPIO;
info->version = "0.0.1";
info->irq = pdev->irq;
info->irq_flags = IRQF_SHARED;
info->handler = aectc_irq;
print_board_data(pdev, info);
ret = uio_register_device(&pdev->dev, info);
if (ret)
goto out_unmap;
iowrite32(INT_ENABLE, info->priv + INT_ENABLE_ADDR);
iowrite8(INT_MASK_ALL, info->priv + INT_MASK_ADDR);
if (!(ioread8(info->priv + INTA_DRVR_ADDR)
& INTA_ENABLED_FLAG))
dev_err(&pdev->dev, "aectc: interrupts not enabled\n");
pci_set_drvdata(pdev, info);
return 0;
out_unmap:
pci_iounmap(pdev, info->priv);
out_release:
pci_release_regions(pdev);
out_disable:
pci_disable_device(pdev);
out_free:
kfree(info);
return -ENODEV;
}
static void remove(struct pci_dev *pdev)
{
struct uio_info *info = pci_get_drvdata(pdev);
/* disable interrupts */
iowrite8(INT_DISABLE, info->priv + INT_MASK_ADDR);
iowrite32(INT_DISABLE, info->priv + INT_ENABLE_ADDR);
/* read mailbox to ensure board drops irq */
ioread8(info->priv + MAILBOX);
uio_unregister_device(info);
pci_release_regions(pdev);
pci_disable_device(pdev);
iounmap(info->priv);
kfree(info);
}
static struct pci_driver pci_driver = {
.name = "aectc",
.id_table = ids,
.probe = probe,
.remove = remove,
};
module_pci_driver(pci_driver);
MODULE_LICENSE("GPL");

140
drivers/uio/uio_cif.c Normal file
View file

@ -0,0 +1,140 @@
/*
* UIO Hilscher CIF card driver
*
* (C) 2007 Hans J. Koch <hjk@hansjkoch.de>
* Original code (C) 2005 Benedikt Spranger <b.spranger@linutronix.de>
*
* Licensed under GPL version 2 only.
*
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/uio_driver.h>
#include <asm/io.h>
#define PLX9030_INTCSR 0x4C
#define INTSCR_INT1_ENABLE 0x01
#define INTSCR_INT1_STATUS 0x04
#define INT1_ENABLED_AND_ACTIVE (INTSCR_INT1_ENABLE | INTSCR_INT1_STATUS)
#define PCI_SUBVENDOR_ID_PEP 0x1518
#define CIF_SUBDEVICE_PROFIBUS 0x430
#define CIF_SUBDEVICE_DEVICENET 0x432
static irqreturn_t hilscher_handler(int irq, struct uio_info *dev_info)
{
void __iomem *plx_intscr = dev_info->mem[0].internal_addr
+ PLX9030_INTCSR;
if ((ioread8(plx_intscr) & INT1_ENABLED_AND_ACTIVE)
!= INT1_ENABLED_AND_ACTIVE)
return IRQ_NONE;
/* Disable interrupt */
iowrite8(ioread8(plx_intscr) & ~INTSCR_INT1_ENABLE, plx_intscr);
return IRQ_HANDLED;
}
static int hilscher_pci_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
struct uio_info *info;
info = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
if (pci_enable_device(dev))
goto out_free;
if (pci_request_regions(dev, "hilscher"))
goto out_disable;
info->mem[0].addr = pci_resource_start(dev, 0);
if (!info->mem[0].addr)
goto out_release;
info->mem[0].internal_addr = pci_ioremap_bar(dev, 0);
if (!info->mem[0].internal_addr)
goto out_release;
info->mem[0].size = pci_resource_len(dev, 0);
info->mem[0].memtype = UIO_MEM_PHYS;
info->mem[1].addr = pci_resource_start(dev, 2);
info->mem[1].size = pci_resource_len(dev, 2);
info->mem[1].memtype = UIO_MEM_PHYS;
switch (id->subdevice) {
case CIF_SUBDEVICE_PROFIBUS:
info->name = "CIF_Profibus";
break;
case CIF_SUBDEVICE_DEVICENET:
info->name = "CIF_Devicenet";
break;
default:
info->name = "CIF_???";
}
info->version = "0.0.1";
info->irq = dev->irq;
info->irq_flags = IRQF_SHARED;
info->handler = hilscher_handler;
if (uio_register_device(&dev->dev, info))
goto out_unmap;
pci_set_drvdata(dev, info);
return 0;
out_unmap:
iounmap(info->mem[0].internal_addr);
out_release:
pci_release_regions(dev);
out_disable:
pci_disable_device(dev);
out_free:
kfree (info);
return -ENODEV;
}
static void hilscher_pci_remove(struct pci_dev *dev)
{
struct uio_info *info = pci_get_drvdata(dev);
uio_unregister_device(info);
pci_release_regions(dev);
pci_disable_device(dev);
iounmap(info->mem[0].internal_addr);
kfree (info);
}
static struct pci_device_id hilscher_pci_ids[] = {
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = PCI_SUBVENDOR_ID_PEP,
.subdevice = CIF_SUBDEVICE_PROFIBUS,
},
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = PCI_SUBVENDOR_ID_PEP,
.subdevice = CIF_SUBDEVICE_DEVICENET,
},
{ 0, }
};
static struct pci_driver hilscher_pci_driver = {
.name = "hilscher",
.id_table = hilscher_pci_ids,
.probe = hilscher_pci_probe,
.remove = hilscher_pci_remove,
};
module_pci_driver(hilscher_pci_driver);
MODULE_DEVICE_TABLE(pci, hilscher_pci_ids);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Hans J. Koch, Benedikt Spranger");

View file

@ -0,0 +1,357 @@
/*
* drivers/uio/uio_dmem_genirq.c
*
* Userspace I/O platform driver with generic IRQ handling code.
*
* Copyright (C) 2012 Damian Hobson-Garcia
*
* Based on uio_pdrv_genirq.c by Magnus Damm
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*/
#include <linux/platform_device.h>
#include <linux/uio_driver.h>
#include <linux/spinlock.h>
#include <linux/bitops.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/platform_data/uio_dmem_genirq.h>
#include <linux/stringify.h>
#include <linux/pm_runtime.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#define DRIVER_NAME "uio_dmem_genirq"
#define DMEM_MAP_ERROR (~0)
struct uio_dmem_genirq_platdata {
struct uio_info *uioinfo;
spinlock_t lock;
unsigned long flags;
struct platform_device *pdev;
unsigned int dmem_region_start;
unsigned int num_dmem_regions;
void *dmem_region_vaddr[MAX_UIO_MAPS];
struct mutex alloc_lock;
unsigned int refcnt;
};
static int uio_dmem_genirq_open(struct uio_info *info, struct inode *inode)
{
struct uio_dmem_genirq_platdata *priv = info->priv;
struct uio_mem *uiomem;
int ret = 0;
int dmem_region = priv->dmem_region_start;
uiomem = &priv->uioinfo->mem[priv->dmem_region_start];
mutex_lock(&priv->alloc_lock);
while (!priv->refcnt && uiomem < &priv->uioinfo->mem[MAX_UIO_MAPS]) {
void *addr;
if (!uiomem->size)
break;
addr = dma_alloc_coherent(&priv->pdev->dev, uiomem->size,
(dma_addr_t *)&uiomem->addr, GFP_KERNEL);
if (!addr) {
uiomem->addr = DMEM_MAP_ERROR;
}
priv->dmem_region_vaddr[dmem_region++] = addr;
++uiomem;
}
priv->refcnt++;
mutex_unlock(&priv->alloc_lock);
/* Wait until the Runtime PM code has woken up the device */
pm_runtime_get_sync(&priv->pdev->dev);
return ret;
}
static int uio_dmem_genirq_release(struct uio_info *info, struct inode *inode)
{
struct uio_dmem_genirq_platdata *priv = info->priv;
struct uio_mem *uiomem;
int dmem_region = priv->dmem_region_start;
/* Tell the Runtime PM code that the device has become idle */
pm_runtime_put_sync(&priv->pdev->dev);
uiomem = &priv->uioinfo->mem[priv->dmem_region_start];
mutex_lock(&priv->alloc_lock);
priv->refcnt--;
while (!priv->refcnt && uiomem < &priv->uioinfo->mem[MAX_UIO_MAPS]) {
if (!uiomem->size)
break;
if (priv->dmem_region_vaddr[dmem_region]) {
dma_free_coherent(&priv->pdev->dev, uiomem->size,
priv->dmem_region_vaddr[dmem_region],
uiomem->addr);
}
uiomem->addr = DMEM_MAP_ERROR;
++dmem_region;
++uiomem;
}
mutex_unlock(&priv->alloc_lock);
return 0;
}
static irqreturn_t uio_dmem_genirq_handler(int irq, struct uio_info *dev_info)
{
struct uio_dmem_genirq_platdata *priv = dev_info->priv;
/* Just disable the interrupt in the interrupt controller, and
* remember the state so we can allow user space to enable it later.
*/
if (!test_and_set_bit(0, &priv->flags))
disable_irq_nosync(irq);
return IRQ_HANDLED;
}
static int uio_dmem_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on)
{
struct uio_dmem_genirq_platdata *priv = dev_info->priv;
unsigned long flags;
/* Allow user space to enable and disable the interrupt
* in the interrupt controller, but keep track of the
* state to prevent per-irq depth damage.
*
* Serialize this operation to support multiple tasks.
*/
spin_lock_irqsave(&priv->lock, flags);
if (irq_on) {
if (test_and_clear_bit(0, &priv->flags))
enable_irq(dev_info->irq);
} else {
if (!test_and_set_bit(0, &priv->flags))
disable_irq(dev_info->irq);
}
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
static int uio_dmem_genirq_probe(struct platform_device *pdev)
{
struct uio_dmem_genirq_pdata *pdata = dev_get_platdata(&pdev->dev);
struct uio_info *uioinfo = &pdata->uioinfo;
struct uio_dmem_genirq_platdata *priv;
struct uio_mem *uiomem;
int ret = -EINVAL;
int i;
if (pdev->dev.of_node) {
int irq;
/* alloc uioinfo for one device */
uioinfo = kzalloc(sizeof(*uioinfo), GFP_KERNEL);
if (!uioinfo) {
ret = -ENOMEM;
dev_err(&pdev->dev, "unable to kmalloc\n");
goto bad2;
}
uioinfo->name = pdev->dev.of_node->name;
uioinfo->version = "devicetree";
/* Multiple IRQs are not supported */
irq = platform_get_irq(pdev, 0);
if (irq == -ENXIO)
uioinfo->irq = UIO_IRQ_NONE;
else
uioinfo->irq = irq;
}
if (!uioinfo || !uioinfo->name || !uioinfo->version) {
dev_err(&pdev->dev, "missing platform_data\n");
goto bad0;
}
if (uioinfo->handler || uioinfo->irqcontrol ||
uioinfo->irq_flags & IRQF_SHARED) {
dev_err(&pdev->dev, "interrupt configuration error\n");
goto bad0;
}
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
ret = -ENOMEM;
dev_err(&pdev->dev, "unable to kmalloc\n");
goto bad0;
}
dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
priv->uioinfo = uioinfo;
spin_lock_init(&priv->lock);
priv->flags = 0; /* interrupt is enabled to begin with */
priv->pdev = pdev;
mutex_init(&priv->alloc_lock);
if (!uioinfo->irq) {
ret = platform_get_irq(pdev, 0);
if (ret < 0) {
dev_err(&pdev->dev, "failed to get IRQ\n");
goto bad1;
}
uioinfo->irq = ret;
}
uiomem = &uioinfo->mem[0];
for (i = 0; i < pdev->num_resources; ++i) {
struct resource *r = &pdev->resource[i];
if (r->flags != IORESOURCE_MEM)
continue;
if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) {
dev_warn(&pdev->dev, "device has more than "
__stringify(MAX_UIO_MAPS)
" I/O memory resources.\n");
break;
}
uiomem->memtype = UIO_MEM_PHYS;
uiomem->addr = r->start;
uiomem->size = resource_size(r);
++uiomem;
}
priv->dmem_region_start = i;
priv->num_dmem_regions = pdata->num_dynamic_regions;
for (i = 0; i < pdata->num_dynamic_regions; ++i) {
if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) {
dev_warn(&pdev->dev, "device has more than "
__stringify(MAX_UIO_MAPS)
" dynamic and fixed memory regions.\n");
break;
}
uiomem->memtype = UIO_MEM_PHYS;
uiomem->addr = DMEM_MAP_ERROR;
uiomem->size = pdata->dynamic_region_sizes[i];
++uiomem;
}
while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) {
uiomem->size = 0;
++uiomem;
}
/* This driver requires no hardware specific kernel code to handle
* interrupts. Instead, the interrupt handler simply disables the
* interrupt in the interrupt controller. User space is responsible
* for performing hardware specific acknowledge and re-enabling of
* the interrupt in the interrupt controller.
*
* Interrupt sharing is not supported.
*/
uioinfo->handler = uio_dmem_genirq_handler;
uioinfo->irqcontrol = uio_dmem_genirq_irqcontrol;
uioinfo->open = uio_dmem_genirq_open;
uioinfo->release = uio_dmem_genirq_release;
uioinfo->priv = priv;
/* Enable Runtime PM for this device:
* The device starts in suspended state to allow the hardware to be
* turned off by default. The Runtime PM bus code should power on the
* hardware and enable clocks at open().
*/
pm_runtime_enable(&pdev->dev);
ret = uio_register_device(&pdev->dev, priv->uioinfo);
if (ret) {
dev_err(&pdev->dev, "unable to register uio device\n");
pm_runtime_disable(&pdev->dev);
goto bad1;
}
platform_set_drvdata(pdev, priv);
return 0;
bad1:
kfree(priv);
bad0:
/* kfree uioinfo for OF */
if (pdev->dev.of_node)
kfree(uioinfo);
bad2:
return ret;
}
static int uio_dmem_genirq_remove(struct platform_device *pdev)
{
struct uio_dmem_genirq_platdata *priv = platform_get_drvdata(pdev);
uio_unregister_device(priv->uioinfo);
pm_runtime_disable(&pdev->dev);
priv->uioinfo->handler = NULL;
priv->uioinfo->irqcontrol = NULL;
/* kfree uioinfo for OF */
if (pdev->dev.of_node)
kfree(priv->uioinfo);
kfree(priv);
return 0;
}
static int uio_dmem_genirq_runtime_nop(struct device *dev)
{
/* Runtime PM callback shared between ->runtime_suspend()
* and ->runtime_resume(). Simply returns success.
*
* In this driver pm_runtime_get_sync() and pm_runtime_put_sync()
* are used at open() and release() time. This allows the
* Runtime PM code to turn off power to the device while the
* device is unused, ie before open() and after release().
*
* This Runtime PM callback does not need to save or restore
* any registers since user space is responsbile for hardware
* register reinitialization after open().
*/
return 0;
}
static const struct dev_pm_ops uio_dmem_genirq_dev_pm_ops = {
.runtime_suspend = uio_dmem_genirq_runtime_nop,
.runtime_resume = uio_dmem_genirq_runtime_nop,
};
#ifdef CONFIG_OF
static const struct of_device_id uio_of_genirq_match[] = {
{ /* empty for now */ },
};
MODULE_DEVICE_TABLE(of, uio_of_genirq_match);
#endif
static struct platform_driver uio_dmem_genirq = {
.probe = uio_dmem_genirq_probe,
.remove = uio_dmem_genirq_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.pm = &uio_dmem_genirq_dev_pm_ops,
.of_match_table = of_match_ptr(uio_of_genirq_match),
},
};
module_platform_driver(uio_dmem_genirq);
MODULE_AUTHOR("Damian Hobson-Garcia");
MODULE_DESCRIPTION("Userspace I/O platform driver with dynamic memory.");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRIVER_NAME);

246
drivers/uio/uio_mf624.c Normal file
View file

@ -0,0 +1,246 @@
/*
* UIO driver fo Humusoft MF624 DAQ card.
* Copyright (C) 2011 Rostislav Lisovy <lisovy@gmail.com>,
* Czech Technical University in Prague
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/uio_driver.h>
#define PCI_VENDOR_ID_HUMUSOFT 0x186c
#define PCI_DEVICE_ID_MF624 0x0624
#define PCI_SUBVENDOR_ID_HUMUSOFT 0x186c
#define PCI_SUBDEVICE_DEVICE 0x0624
/* BAR0 Interrupt control/status register */
#define INTCSR 0x4C
#define INTCSR_ADINT_ENABLE (1 << 0)
#define INTCSR_CTR4INT_ENABLE (1 << 3)
#define INTCSR_PCIINT_ENABLE (1 << 6)
#define INTCSR_ADINT_STATUS (1 << 2)
#define INTCSR_CTR4INT_STATUS (1 << 5)
enum mf624_interrupt_source {ADC, CTR4, ALL};
static void mf624_disable_interrupt(enum mf624_interrupt_source source,
struct uio_info *info)
{
void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR;
switch (source) {
case ADC:
iowrite32(ioread32(INTCSR_reg)
& ~(INTCSR_ADINT_ENABLE | INTCSR_PCIINT_ENABLE),
INTCSR_reg);
break;
case CTR4:
iowrite32(ioread32(INTCSR_reg)
& ~(INTCSR_CTR4INT_ENABLE | INTCSR_PCIINT_ENABLE),
INTCSR_reg);
break;
case ALL:
default:
iowrite32(ioread32(INTCSR_reg)
& ~(INTCSR_ADINT_ENABLE | INTCSR_CTR4INT_ENABLE
| INTCSR_PCIINT_ENABLE),
INTCSR_reg);
break;
}
}
static void mf624_enable_interrupt(enum mf624_interrupt_source source,
struct uio_info *info)
{
void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR;
switch (source) {
case ADC:
iowrite32(ioread32(INTCSR_reg)
| INTCSR_ADINT_ENABLE | INTCSR_PCIINT_ENABLE,
INTCSR_reg);
break;
case CTR4:
iowrite32(ioread32(INTCSR_reg)
| INTCSR_CTR4INT_ENABLE | INTCSR_PCIINT_ENABLE,
INTCSR_reg);
break;
case ALL:
default:
iowrite32(ioread32(INTCSR_reg)
| INTCSR_ADINT_ENABLE | INTCSR_CTR4INT_ENABLE
| INTCSR_PCIINT_ENABLE,
INTCSR_reg);
break;
}
}
static irqreturn_t mf624_irq_handler(int irq, struct uio_info *info)
{
void __iomem *INTCSR_reg = info->mem[0].internal_addr + INTCSR;
if ((ioread32(INTCSR_reg) & INTCSR_ADINT_ENABLE)
&& (ioread32(INTCSR_reg) & INTCSR_ADINT_STATUS)) {
mf624_disable_interrupt(ADC, info);
return IRQ_HANDLED;
}
if ((ioread32(INTCSR_reg) & INTCSR_CTR4INT_ENABLE)
&& (ioread32(INTCSR_reg) & INTCSR_CTR4INT_STATUS)) {
mf624_disable_interrupt(CTR4, info);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int mf624_irqcontrol(struct uio_info *info, s32 irq_on)
{
if (irq_on == 0)
mf624_disable_interrupt(ALL, info);
else if (irq_on == 1)
mf624_enable_interrupt(ALL, info);
return 0;
}
static int mf624_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
struct uio_info *info;
info = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
if (pci_enable_device(dev))
goto out_free;
if (pci_request_regions(dev, "mf624"))
goto out_disable;
info->name = "mf624";
info->version = "0.0.1";
/* Note: Datasheet says device uses BAR0, BAR1, BAR2 -- do not trust it */
/* BAR0 */
info->mem[0].name = "PCI chipset, interrupts, status "
"bits, special functions";
info->mem[0].addr = pci_resource_start(dev, 0);
if (!info->mem[0].addr)
goto out_release;
info->mem[0].size = pci_resource_len(dev, 0);
info->mem[0].memtype = UIO_MEM_PHYS;
info->mem[0].internal_addr = pci_ioremap_bar(dev, 0);
if (!info->mem[0].internal_addr)
goto out_release;
/* BAR2 */
info->mem[1].name = "ADC, DAC, DIO";
info->mem[1].addr = pci_resource_start(dev, 2);
if (!info->mem[1].addr)
goto out_unmap0;
info->mem[1].size = pci_resource_len(dev, 2);
info->mem[1].memtype = UIO_MEM_PHYS;
info->mem[1].internal_addr = pci_ioremap_bar(dev, 2);
if (!info->mem[1].internal_addr)
goto out_unmap0;
/* BAR4 */
info->mem[2].name = "Counter/timer chip";
info->mem[2].addr = pci_resource_start(dev, 4);
if (!info->mem[2].addr)
goto out_unmap1;
info->mem[2].size = pci_resource_len(dev, 4);
info->mem[2].memtype = UIO_MEM_PHYS;
info->mem[2].internal_addr = pci_ioremap_bar(dev, 4);
if (!info->mem[2].internal_addr)
goto out_unmap1;
info->irq = dev->irq;
info->irq_flags = IRQF_SHARED;
info->handler = mf624_irq_handler;
info->irqcontrol = mf624_irqcontrol;
if (uio_register_device(&dev->dev, info))
goto out_unmap2;
pci_set_drvdata(dev, info);
return 0;
out_unmap2:
iounmap(info->mem[2].internal_addr);
out_unmap1:
iounmap(info->mem[1].internal_addr);
out_unmap0:
iounmap(info->mem[0].internal_addr);
out_release:
pci_release_regions(dev);
out_disable:
pci_disable_device(dev);
out_free:
kfree(info);
return -ENODEV;
}
static void mf624_pci_remove(struct pci_dev *dev)
{
struct uio_info *info = pci_get_drvdata(dev);
mf624_disable_interrupt(ALL, info);
uio_unregister_device(info);
pci_release_regions(dev);
pci_disable_device(dev);
iounmap(info->mem[0].internal_addr);
iounmap(info->mem[1].internal_addr);
iounmap(info->mem[2].internal_addr);
kfree(info);
}
static const struct pci_device_id mf624_pci_id[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_HUMUSOFT, PCI_DEVICE_ID_MF624) },
{ 0, }
};
static struct pci_driver mf624_pci_driver = {
.name = "mf624",
.id_table = mf624_pci_id,
.probe = mf624_pci_probe,
.remove = mf624_pci_remove,
};
MODULE_DEVICE_TABLE(pci, mf624_pci_id);
module_pci_driver(mf624_pci_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Rostislav Lisovy <lisovy@gmail.com>");

179
drivers/uio/uio_netx.c Normal file
View file

@ -0,0 +1,179 @@
/*
* UIO driver for Hilscher NetX based fieldbus cards (cifX, comX).
* See http://www.hilscher.com for details.
*
* (C) 2007 Hans J. Koch <hjk@hansjkoch.de>
* (C) 2008 Manuel Traut <manut@linutronix.de>
*
* Licensed under GPL version 2 only.
*
*/
#include <linux/device.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/uio_driver.h>
#define PCI_VENDOR_ID_HILSCHER 0x15CF
#define PCI_DEVICE_ID_HILSCHER_NETX 0x0000
#define PCI_DEVICE_ID_HILSCHER_NETPLC 0x0010
#define PCI_SUBDEVICE_ID_NETPLC_RAM 0x0000
#define PCI_SUBDEVICE_ID_NETPLC_FLASH 0x0001
#define PCI_SUBDEVICE_ID_NXSB_PCA 0x3235
#define PCI_SUBDEVICE_ID_NXPCA 0x3335
#define DPM_HOST_INT_EN0 0xfff0
#define DPM_HOST_INT_STAT0 0xffe0
#define DPM_HOST_INT_MASK 0xe600ffff
#define DPM_HOST_INT_GLOBAL_EN 0x80000000
static irqreturn_t netx_handler(int irq, struct uio_info *dev_info)
{
void __iomem *int_enable_reg = dev_info->mem[0].internal_addr
+ DPM_HOST_INT_EN0;
void __iomem *int_status_reg = dev_info->mem[0].internal_addr
+ DPM_HOST_INT_STAT0;
/* Is one of our interrupts enabled and active ? */
if (!(ioread32(int_enable_reg) & ioread32(int_status_reg)
& DPM_HOST_INT_MASK))
return IRQ_NONE;
/* Disable interrupt */
iowrite32(ioread32(int_enable_reg) & ~DPM_HOST_INT_GLOBAL_EN,
int_enable_reg);
return IRQ_HANDLED;
}
static int netx_pci_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
struct uio_info *info;
int bar;
info = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
if (pci_enable_device(dev))
goto out_free;
if (pci_request_regions(dev, "netx"))
goto out_disable;
switch (id->device) {
case PCI_DEVICE_ID_HILSCHER_NETX:
bar = 0;
info->name = "netx";
break;
case PCI_DEVICE_ID_HILSCHER_NETPLC:
bar = 0;
info->name = "netplc";
break;
default:
bar = 2;
info->name = "netx_plx";
}
/* BAR0 or 2 points to the card's dual port memory */
info->mem[0].addr = pci_resource_start(dev, bar);
if (!info->mem[0].addr)
goto out_release;
info->mem[0].internal_addr = ioremap(pci_resource_start(dev, bar),
pci_resource_len(dev, bar));
if (!info->mem[0].internal_addr)
goto out_release;
info->mem[0].size = pci_resource_len(dev, bar);
info->mem[0].memtype = UIO_MEM_PHYS;
info->irq = dev->irq;
info->irq_flags = IRQF_SHARED;
info->handler = netx_handler;
info->version = "0.0.1";
/* Make sure all interrupts are disabled */
iowrite32(0, info->mem[0].internal_addr + DPM_HOST_INT_EN0);
if (uio_register_device(&dev->dev, info))
goto out_unmap;
pci_set_drvdata(dev, info);
dev_info(&dev->dev, "Found %s card, registered UIO device.\n",
info->name);
return 0;
out_unmap:
iounmap(info->mem[0].internal_addr);
out_release:
pci_release_regions(dev);
out_disable:
pci_disable_device(dev);
out_free:
kfree(info);
return -ENODEV;
}
static void netx_pci_remove(struct pci_dev *dev)
{
struct uio_info *info = pci_get_drvdata(dev);
/* Disable all interrupts */
iowrite32(0, info->mem[0].internal_addr + DPM_HOST_INT_EN0);
uio_unregister_device(info);
pci_release_regions(dev);
pci_disable_device(dev);
iounmap(info->mem[0].internal_addr);
kfree(info);
}
static struct pci_device_id netx_pci_ids[] = {
{
.vendor = PCI_VENDOR_ID_HILSCHER,
.device = PCI_DEVICE_ID_HILSCHER_NETX,
.subvendor = 0,
.subdevice = 0,
},
{
.vendor = PCI_VENDOR_ID_HILSCHER,
.device = PCI_DEVICE_ID_HILSCHER_NETPLC,
.subvendor = PCI_VENDOR_ID_HILSCHER,
.subdevice = PCI_SUBDEVICE_ID_NETPLC_RAM,
},
{
.vendor = PCI_VENDOR_ID_HILSCHER,
.device = PCI_DEVICE_ID_HILSCHER_NETPLC,
.subvendor = PCI_VENDOR_ID_HILSCHER,
.subdevice = PCI_SUBDEVICE_ID_NETPLC_FLASH,
},
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = PCI_VENDOR_ID_PLX,
.subdevice = PCI_SUBDEVICE_ID_NXSB_PCA,
},
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = PCI_VENDOR_ID_PLX,
.subdevice = PCI_SUBDEVICE_ID_NXPCA,
},
{ 0, }
};
static struct pci_driver netx_pci_driver = {
.name = "netx",
.id_table = netx_pci_ids,
.probe = netx_pci_probe,
.remove = netx_pci_remove,
};
module_pci_driver(netx_pci_driver);
MODULE_DEVICE_TABLE(pci, netx_pci_ids);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Hans J. Koch, Manuel Traut");

View file

@ -0,0 +1,127 @@
/* uio_pci_generic - generic UIO driver for PCI 2.3 devices
*
* Copyright (C) 2009 Red Hat, Inc.
* Author: Michael S. Tsirkin <mst@redhat.com>
*
* This work is licensed under the terms of the GNU GPL, version 2.
*
* Since the driver does not declare any device ids, you must allocate
* id and bind the device to the driver yourself. For example:
*
* # echo "8086 10f5" > /sys/bus/pci/drivers/uio_pci_generic/new_id
* # echo -n 0000:00:19.0 > /sys/bus/pci/drivers/e1000e/unbind
* # echo -n 0000:00:19.0 > /sys/bus/pci/drivers/uio_pci_generic/bind
* # ls -l /sys/bus/pci/devices/0000:00:19.0/driver
* .../0000:00:19.0/driver -> ../../../bus/pci/drivers/uio_pci_generic
*
* Driver won't bind to devices which do not support the Interrupt Disable Bit
* in the command register. All devices compliant to PCI 2.3 (circa 2002) and
* all compliant PCI Express devices should support this bit.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/uio_driver.h>
#define DRIVER_VERSION "0.01.0"
#define DRIVER_AUTHOR "Michael S. Tsirkin <mst@redhat.com>"
#define DRIVER_DESC "Generic UIO driver for PCI 2.3 devices"
struct uio_pci_generic_dev {
struct uio_info info;
struct pci_dev *pdev;
};
static inline struct uio_pci_generic_dev *
to_uio_pci_generic_dev(struct uio_info *info)
{
return container_of(info, struct uio_pci_generic_dev, info);
}
/* Interrupt handler. Read/modify/write the command register to disable
* the interrupt. */
static irqreturn_t irqhandler(int irq, struct uio_info *info)
{
struct uio_pci_generic_dev *gdev = to_uio_pci_generic_dev(info);
if (!pci_check_and_mask_intx(gdev->pdev))
return IRQ_NONE;
/* UIO core will signal the user process. */
return IRQ_HANDLED;
}
static int probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct uio_pci_generic_dev *gdev;
int err;
err = pci_enable_device(pdev);
if (err) {
dev_err(&pdev->dev, "%s: pci_enable_device failed: %d\n",
__func__, err);
return err;
}
if (!pdev->irq) {
dev_warn(&pdev->dev, "No IRQ assigned to device: "
"no support for interrupts?\n");
pci_disable_device(pdev);
return -ENODEV;
}
if (!pci_intx_mask_supported(pdev)) {
err = -ENODEV;
goto err_verify;
}
gdev = kzalloc(sizeof(struct uio_pci_generic_dev), GFP_KERNEL);
if (!gdev) {
err = -ENOMEM;
goto err_alloc;
}
gdev->info.name = "uio_pci_generic";
gdev->info.version = DRIVER_VERSION;
gdev->info.irq = pdev->irq;
gdev->info.irq_flags = IRQF_SHARED;
gdev->info.handler = irqhandler;
gdev->pdev = pdev;
if (uio_register_device(&pdev->dev, &gdev->info))
goto err_register;
pci_set_drvdata(pdev, gdev);
return 0;
err_register:
kfree(gdev);
err_alloc:
err_verify:
pci_disable_device(pdev);
return err;
}
static void remove(struct pci_dev *pdev)
{
struct uio_pci_generic_dev *gdev = pci_get_drvdata(pdev);
uio_unregister_device(&gdev->info);
pci_disable_device(pdev);
kfree(gdev);
}
static struct pci_driver uio_pci_driver = {
.name = "uio_pci_generic",
.id_table = NULL, /* only dynamic id's */
.probe = probe,
.remove = remove,
};
module_pci_driver(uio_pci_driver);
MODULE_VERSION(DRIVER_VERSION);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);

View file

@ -0,0 +1,280 @@
/*
* drivers/uio/uio_pdrv_genirq.c
*
* Userspace I/O platform driver with generic IRQ handling code.
*
* Copyright (C) 2008 Magnus Damm
*
* Based on uio_pdrv.c by Uwe Kleine-Koenig,
* Copyright (C) 2008 by Digi International Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*/
#include <linux/platform_device.h>
#include <linux/uio_driver.h>
#include <linux/spinlock.h>
#include <linux/bitops.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/stringify.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/of_address.h>
#define DRIVER_NAME "uio_pdrv_genirq"
struct uio_pdrv_genirq_platdata {
struct uio_info *uioinfo;
spinlock_t lock;
unsigned long flags;
struct platform_device *pdev;
};
/* Bits in uio_pdrv_genirq_platdata.flags */
enum {
UIO_IRQ_DISABLED = 0,
};
static int uio_pdrv_genirq_open(struct uio_info *info, struct inode *inode)
{
struct uio_pdrv_genirq_platdata *priv = info->priv;
/* Wait until the Runtime PM code has woken up the device */
pm_runtime_get_sync(&priv->pdev->dev);
return 0;
}
static int uio_pdrv_genirq_release(struct uio_info *info, struct inode *inode)
{
struct uio_pdrv_genirq_platdata *priv = info->priv;
/* Tell the Runtime PM code that the device has become idle */
pm_runtime_put_sync(&priv->pdev->dev);
return 0;
}
static irqreturn_t uio_pdrv_genirq_handler(int irq, struct uio_info *dev_info)
{
struct uio_pdrv_genirq_platdata *priv = dev_info->priv;
/* Just disable the interrupt in the interrupt controller, and
* remember the state so we can allow user space to enable it later.
*/
spin_lock(&priv->lock);
if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags))
disable_irq_nosync(irq);
spin_unlock(&priv->lock);
return IRQ_HANDLED;
}
static int uio_pdrv_genirq_irqcontrol(struct uio_info *dev_info, s32 irq_on)
{
struct uio_pdrv_genirq_platdata *priv = dev_info->priv;
unsigned long flags;
/* Allow user space to enable and disable the interrupt
* in the interrupt controller, but keep track of the
* state to prevent per-irq depth damage.
*
* Serialize this operation to support multiple tasks and concurrency
* with irq handler on SMP systems.
*/
spin_lock_irqsave(&priv->lock, flags);
if (irq_on) {
if (__test_and_clear_bit(UIO_IRQ_DISABLED, &priv->flags))
enable_irq(dev_info->irq);
} else {
if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags))
disable_irq_nosync(dev_info->irq);
}
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
static int uio_pdrv_genirq_probe(struct platform_device *pdev)
{
struct uio_info *uioinfo = dev_get_platdata(&pdev->dev);
struct uio_pdrv_genirq_platdata *priv;
struct uio_mem *uiomem;
int ret = -EINVAL;
int i;
if (pdev->dev.of_node) {
/* alloc uioinfo for one device */
uioinfo = devm_kzalloc(&pdev->dev, sizeof(*uioinfo),
GFP_KERNEL);
if (!uioinfo) {
dev_err(&pdev->dev, "unable to kmalloc\n");
return -ENOMEM;
}
uioinfo->name = pdev->dev.of_node->name;
uioinfo->version = "devicetree";
/* Multiple IRQs are not supported */
}
if (!uioinfo || !uioinfo->name || !uioinfo->version) {
dev_err(&pdev->dev, "missing platform_data\n");
return ret;
}
if (uioinfo->handler || uioinfo->irqcontrol ||
uioinfo->irq_flags & IRQF_SHARED) {
dev_err(&pdev->dev, "interrupt configuration error\n");
return ret;
}
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv) {
dev_err(&pdev->dev, "unable to kmalloc\n");
return -ENOMEM;
}
priv->uioinfo = uioinfo;
spin_lock_init(&priv->lock);
priv->flags = 0; /* interrupt is enabled to begin with */
priv->pdev = pdev;
if (!uioinfo->irq) {
ret = platform_get_irq(pdev, 0);
uioinfo->irq = ret;
if (ret == -ENXIO && pdev->dev.of_node)
uioinfo->irq = UIO_IRQ_NONE;
else if (ret < 0) {
dev_err(&pdev->dev, "failed to get IRQ\n");
return ret;
}
}
uiomem = &uioinfo->mem[0];
for (i = 0; i < pdev->num_resources; ++i) {
struct resource *r = &pdev->resource[i];
if (r->flags != IORESOURCE_MEM)
continue;
if (uiomem >= &uioinfo->mem[MAX_UIO_MAPS]) {
dev_warn(&pdev->dev, "device has more than "
__stringify(MAX_UIO_MAPS)
" I/O memory resources.\n");
break;
}
uiomem->memtype = UIO_MEM_PHYS;
uiomem->addr = r->start;
uiomem->size = resource_size(r);
uiomem->name = r->name;
++uiomem;
}
while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) {
uiomem->size = 0;
++uiomem;
}
/* This driver requires no hardware specific kernel code to handle
* interrupts. Instead, the interrupt handler simply disables the
* interrupt in the interrupt controller. User space is responsible
* for performing hardware specific acknowledge and re-enabling of
* the interrupt in the interrupt controller.
*
* Interrupt sharing is not supported.
*/
uioinfo->handler = uio_pdrv_genirq_handler;
uioinfo->irqcontrol = uio_pdrv_genirq_irqcontrol;
uioinfo->open = uio_pdrv_genirq_open;
uioinfo->release = uio_pdrv_genirq_release;
uioinfo->priv = priv;
/* Enable Runtime PM for this device:
* The device starts in suspended state to allow the hardware to be
* turned off by default. The Runtime PM bus code should power on the
* hardware and enable clocks at open().
*/
pm_runtime_enable(&pdev->dev);
ret = uio_register_device(&pdev->dev, priv->uioinfo);
if (ret) {
dev_err(&pdev->dev, "unable to register uio device\n");
pm_runtime_disable(&pdev->dev);
return ret;
}
platform_set_drvdata(pdev, priv);
return 0;
}
static int uio_pdrv_genirq_remove(struct platform_device *pdev)
{
struct uio_pdrv_genirq_platdata *priv = platform_get_drvdata(pdev);
uio_unregister_device(priv->uioinfo);
pm_runtime_disable(&pdev->dev);
priv->uioinfo->handler = NULL;
priv->uioinfo->irqcontrol = NULL;
return 0;
}
static int uio_pdrv_genirq_runtime_nop(struct device *dev)
{
/* Runtime PM callback shared between ->runtime_suspend()
* and ->runtime_resume(). Simply returns success.
*
* In this driver pm_runtime_get_sync() and pm_runtime_put_sync()
* are used at open() and release() time. This allows the
* Runtime PM code to turn off power to the device while the
* device is unused, ie before open() and after release().
*
* This Runtime PM callback does not need to save or restore
* any registers since user space is responsbile for hardware
* register reinitialization after open().
*/
return 0;
}
static const struct dev_pm_ops uio_pdrv_genirq_dev_pm_ops = {
.runtime_suspend = uio_pdrv_genirq_runtime_nop,
.runtime_resume = uio_pdrv_genirq_runtime_nop,
};
#ifdef CONFIG_OF
static struct of_device_id uio_of_genirq_match[] = {
{ /* This is filled with module_parm */ },
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, uio_of_genirq_match);
module_param_string(of_id, uio_of_genirq_match[0].compatible, 128, 0);
MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio");
#endif
static struct platform_driver uio_pdrv_genirq = {
.probe = uio_pdrv_genirq_probe,
.remove = uio_pdrv_genirq_remove,
.driver = {
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.pm = &uio_pdrv_genirq_dev_pm_ops,
.of_match_table = of_match_ptr(uio_of_genirq_match),
},
};
module_platform_driver(uio_pdrv_genirq);
MODULE_AUTHOR("Magnus Damm");
MODULE_DESCRIPTION("Userspace I/O platform driver with generic IRQ handling");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" DRIVER_NAME);

243
drivers/uio/uio_pruss.c Normal file
View file

@ -0,0 +1,243 @@
/*
* Programmable Real-Time Unit Sub System (PRUSS) UIO driver (uio_pruss)
*
* This driver exports PRUSS host event out interrupts and PRUSS, L3 RAM,
* and DDR RAM to user space for applications interacting with PRUSS firmware
*
* Copyright (C) 2010-11 Texas Instruments Incorporated - http://www.ti.com/
*
* 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 version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <linux/uio_driver.h>
#include <linux/platform_data/uio_pruss.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/genalloc.h>
#define DRV_NAME "pruss_uio"
#define DRV_VERSION "1.0"
static int sram_pool_sz = SZ_16K;
module_param(sram_pool_sz, int, 0);
MODULE_PARM_DESC(sram_pool_sz, "sram pool size to allocate ");
static int extram_pool_sz = SZ_256K;
module_param(extram_pool_sz, int, 0);
MODULE_PARM_DESC(extram_pool_sz, "external ram pool size to allocate");
/*
* Host event IRQ numbers from PRUSS - PRUSS can generate up to 8 interrupt
* events to AINTC of ARM host processor - which can be used for IPC b/w PRUSS
* firmware and user space application, async notification from PRU firmware
* to user space application
* 3 PRU_EVTOUT0
* 4 PRU_EVTOUT1
* 5 PRU_EVTOUT2
* 6 PRU_EVTOUT3
* 7 PRU_EVTOUT4
* 8 PRU_EVTOUT5
* 9 PRU_EVTOUT6
* 10 PRU_EVTOUT7
*/
#define MAX_PRUSS_EVT 8
#define PINTC_HIDISR 0x0038
#define PINTC_HIPIR 0x0900
#define HIPIR_NOPEND 0x80000000
#define PINTC_HIER 0x1500
struct uio_pruss_dev {
struct uio_info *info;
struct clk *pruss_clk;
dma_addr_t sram_paddr;
dma_addr_t ddr_paddr;
void __iomem *prussio_vaddr;
unsigned long sram_vaddr;
void *ddr_vaddr;
unsigned int hostirq_start;
unsigned int pintc_base;
struct gen_pool *sram_pool;
};
static irqreturn_t pruss_handler(int irq, struct uio_info *info)
{
struct uio_pruss_dev *gdev = info->priv;
int intr_bit = (irq - gdev->hostirq_start + 2);
int val, intr_mask = (1 << intr_bit);
void __iomem *base = gdev->prussio_vaddr + gdev->pintc_base;
void __iomem *intren_reg = base + PINTC_HIER;
void __iomem *intrdis_reg = base + PINTC_HIDISR;
void __iomem *intrstat_reg = base + PINTC_HIPIR + (intr_bit << 2);
val = ioread32(intren_reg);
/* Is interrupt enabled and active ? */
if (!(val & intr_mask) && (ioread32(intrstat_reg) & HIPIR_NOPEND))
return IRQ_NONE;
/* Disable interrupt */
iowrite32(intr_bit, intrdis_reg);
return IRQ_HANDLED;
}
static void pruss_cleanup(struct device *dev, struct uio_pruss_dev *gdev)
{
int cnt;
struct uio_info *p = gdev->info;
for (cnt = 0; cnt < MAX_PRUSS_EVT; cnt++, p++) {
uio_unregister_device(p);
kfree(p->name);
}
iounmap(gdev->prussio_vaddr);
if (gdev->ddr_vaddr) {
dma_free_coherent(dev, extram_pool_sz, gdev->ddr_vaddr,
gdev->ddr_paddr);
}
if (gdev->sram_vaddr)
gen_pool_free(gdev->sram_pool,
gdev->sram_vaddr,
sram_pool_sz);
kfree(gdev->info);
clk_put(gdev->pruss_clk);
kfree(gdev);
}
static int pruss_probe(struct platform_device *pdev)
{
struct uio_info *p;
struct uio_pruss_dev *gdev;
struct resource *regs_prussio;
struct device *dev = &pdev->dev;
int ret = -ENODEV, cnt = 0, len;
struct uio_pruss_pdata *pdata = dev_get_platdata(dev);
gdev = kzalloc(sizeof(struct uio_pruss_dev), GFP_KERNEL);
if (!gdev)
return -ENOMEM;
gdev->info = kzalloc(sizeof(*p) * MAX_PRUSS_EVT, GFP_KERNEL);
if (!gdev->info) {
kfree(gdev);
return -ENOMEM;
}
/* Power on PRU in case its not done as part of boot-loader */
gdev->pruss_clk = clk_get(dev, "pruss");
if (IS_ERR(gdev->pruss_clk)) {
dev_err(dev, "Failed to get clock\n");
ret = PTR_ERR(gdev->pruss_clk);
kfree(gdev->info);
kfree(gdev);
return ret;
} else {
clk_enable(gdev->pruss_clk);
}
regs_prussio = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!regs_prussio) {
dev_err(dev, "No PRUSS I/O resource specified\n");
goto out_free;
}
if (!regs_prussio->start) {
dev_err(dev, "Invalid memory resource\n");
goto out_free;
}
if (pdata->sram_pool) {
gdev->sram_pool = pdata->sram_pool;
gdev->sram_vaddr =
(unsigned long)gen_pool_dma_alloc(gdev->sram_pool,
sram_pool_sz, &gdev->sram_paddr);
if (!gdev->sram_vaddr) {
dev_err(dev, "Could not allocate SRAM pool\n");
goto out_free;
}
}
gdev->ddr_vaddr = dma_alloc_coherent(dev, extram_pool_sz,
&(gdev->ddr_paddr), GFP_KERNEL | GFP_DMA);
if (!gdev->ddr_vaddr) {
dev_err(dev, "Could not allocate external memory\n");
goto out_free;
}
len = resource_size(regs_prussio);
gdev->prussio_vaddr = ioremap(regs_prussio->start, len);
if (!gdev->prussio_vaddr) {
dev_err(dev, "Can't remap PRUSS I/O address range\n");
goto out_free;
}
gdev->pintc_base = pdata->pintc_base;
gdev->hostirq_start = platform_get_irq(pdev, 0);
for (cnt = 0, p = gdev->info; cnt < MAX_PRUSS_EVT; cnt++, p++) {
p->mem[0].addr = regs_prussio->start;
p->mem[0].size = resource_size(regs_prussio);
p->mem[0].memtype = UIO_MEM_PHYS;
p->mem[1].addr = gdev->sram_paddr;
p->mem[1].size = sram_pool_sz;
p->mem[1].memtype = UIO_MEM_PHYS;
p->mem[2].addr = gdev->ddr_paddr;
p->mem[2].size = extram_pool_sz;
p->mem[2].memtype = UIO_MEM_PHYS;
p->name = kasprintf(GFP_KERNEL, "pruss_evt%d", cnt);
p->version = DRV_VERSION;
/* Register PRUSS IRQ lines */
p->irq = gdev->hostirq_start + cnt;
p->handler = pruss_handler;
p->priv = gdev;
ret = uio_register_device(dev, p);
if (ret < 0)
goto out_free;
}
platform_set_drvdata(pdev, gdev);
return 0;
out_free:
pruss_cleanup(dev, gdev);
return ret;
}
static int pruss_remove(struct platform_device *dev)
{
struct uio_pruss_dev *gdev = platform_get_drvdata(dev);
pruss_cleanup(&dev->dev, gdev);
return 0;
}
static struct platform_driver pruss_driver = {
.probe = pruss_probe,
.remove = pruss_remove,
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
},
};
module_platform_driver(pruss_driver);
MODULE_LICENSE("GPL v2");
MODULE_VERSION(DRV_VERSION);
MODULE_AUTHOR("Amit Chatterjee <amit.chatterjee@ti.com>");
MODULE_AUTHOR("Pratheesh Gangadhar <pratheesh@ti.com>");

231
drivers/uio/uio_sercos3.c Normal file
View file

@ -0,0 +1,231 @@
/* sercos3: UIO driver for the Automata Sercos III PCI card
Copyright (C) 2008 Linutronix GmbH
Author: John Ogness <john.ogness@linutronix.de>
This is a straight-forward UIO driver, where interrupts are disabled
by the interrupt handler and re-enabled via a write to the UIO device
by the userspace-part.
The only part that may seem odd is the use of a logical OR when
storing and restoring enabled interrupts. This is done because the
userspace-part could directly modify the Interrupt Enable Register
at any time. To reduce possible conflicts, the kernel driver uses
a logical OR to make more controlled changes (rather than blindly
overwriting previous values).
Race conditions exist if the userspace-part directly modifies the
Interrupt Enable Register while in operation. The consequences are
that certain interrupts would fail to be enabled or disabled. For
this reason, the userspace-part should only directly modify the
Interrupt Enable Register at the beginning (to get things going).
The userspace-part can safely disable interrupts at any time using
a write to the UIO device.
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/uio_driver.h>
#include <linux/io.h>
#include <linux/slab.h>
/* ID's for SERCOS III PCI card (PLX 9030) */
#define SERCOS_SUB_VENDOR_ID 0x1971
#define SERCOS_SUB_SYSID_3530 0x3530
#define SERCOS_SUB_SYSID_3535 0x3535
#define SERCOS_SUB_SYSID_3780 0x3780
/* Interrupt Enable Register */
#define IER0_OFFSET 0x08
/* Interrupt Status Register */
#define ISR0_OFFSET 0x18
struct sercos3_priv {
u32 ier0_cache;
spinlock_t ier0_cache_lock;
};
/* this function assumes ier0_cache_lock is locked! */
static void sercos3_disable_interrupts(struct uio_info *info,
struct sercos3_priv *priv)
{
void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET;
/* add enabled interrupts to cache */
priv->ier0_cache |= ioread32(ier0);
/* disable interrupts */
iowrite32(0, ier0);
}
/* this function assumes ier0_cache_lock is locked! */
static void sercos3_enable_interrupts(struct uio_info *info,
struct sercos3_priv *priv)
{
void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET;
/* restore previously enabled interrupts */
iowrite32(ioread32(ier0) | priv->ier0_cache, ier0);
priv->ier0_cache = 0;
}
static irqreturn_t sercos3_handler(int irq, struct uio_info *info)
{
struct sercos3_priv *priv = info->priv;
void __iomem *isr0 = info->mem[3].internal_addr + ISR0_OFFSET;
void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET;
if (!(ioread32(isr0) & ioread32(ier0)))
return IRQ_NONE;
spin_lock(&priv->ier0_cache_lock);
sercos3_disable_interrupts(info, priv);
spin_unlock(&priv->ier0_cache_lock);
return IRQ_HANDLED;
}
static int sercos3_irqcontrol(struct uio_info *info, s32 irq_on)
{
struct sercos3_priv *priv = info->priv;
spin_lock_irq(&priv->ier0_cache_lock);
if (irq_on)
sercos3_enable_interrupts(info, priv);
else
sercos3_disable_interrupts(info, priv);
spin_unlock_irq(&priv->ier0_cache_lock);
return 0;
}
static int sercos3_setup_iomem(struct pci_dev *dev, struct uio_info *info,
int n, int pci_bar)
{
info->mem[n].addr = pci_resource_start(dev, pci_bar);
if (!info->mem[n].addr)
return -1;
info->mem[n].internal_addr = ioremap(pci_resource_start(dev, pci_bar),
pci_resource_len(dev, pci_bar));
if (!info->mem[n].internal_addr)
return -1;
info->mem[n].size = pci_resource_len(dev, pci_bar);
info->mem[n].memtype = UIO_MEM_PHYS;
return 0;
}
static int sercos3_pci_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
struct uio_info *info;
struct sercos3_priv *priv;
int i;
info = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
priv = kzalloc(sizeof(struct sercos3_priv), GFP_KERNEL);
if (!priv)
goto out_free;
if (pci_enable_device(dev))
goto out_free_priv;
if (pci_request_regions(dev, "sercos3"))
goto out_disable;
/* we only need PCI BAR's 0, 2, 3, 4, 5 */
if (sercos3_setup_iomem(dev, info, 0, 0))
goto out_unmap;
if (sercos3_setup_iomem(dev, info, 1, 2))
goto out_unmap;
if (sercos3_setup_iomem(dev, info, 2, 3))
goto out_unmap;
if (sercos3_setup_iomem(dev, info, 3, 4))
goto out_unmap;
if (sercos3_setup_iomem(dev, info, 4, 5))
goto out_unmap;
spin_lock_init(&priv->ier0_cache_lock);
info->priv = priv;
info->name = "Sercos_III_PCI";
info->version = "0.0.1";
info->irq = dev->irq;
info->irq_flags = IRQF_SHARED;
info->handler = sercos3_handler;
info->irqcontrol = sercos3_irqcontrol;
pci_set_drvdata(dev, info);
if (uio_register_device(&dev->dev, info))
goto out_unmap;
return 0;
out_unmap:
for (i = 0; i < 5; i++) {
if (info->mem[i].internal_addr)
iounmap(info->mem[i].internal_addr);
}
pci_release_regions(dev);
out_disable:
pci_disable_device(dev);
out_free_priv:
kfree(priv);
out_free:
kfree(info);
return -ENODEV;
}
static void sercos3_pci_remove(struct pci_dev *dev)
{
struct uio_info *info = pci_get_drvdata(dev);
int i;
uio_unregister_device(info);
pci_release_regions(dev);
pci_disable_device(dev);
for (i = 0; i < 5; i++) {
if (info->mem[i].internal_addr)
iounmap(info->mem[i].internal_addr);
}
kfree(info->priv);
kfree(info);
}
static struct pci_device_id sercos3_pci_ids[] = {
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = SERCOS_SUB_VENDOR_ID,
.subdevice = SERCOS_SUB_SYSID_3530,
},
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = SERCOS_SUB_VENDOR_ID,
.subdevice = SERCOS_SUB_SYSID_3535,
},
{
.vendor = PCI_VENDOR_ID_PLX,
.device = PCI_DEVICE_ID_PLX_9030,
.subvendor = SERCOS_SUB_VENDOR_ID,
.subdevice = SERCOS_SUB_SYSID_3780,
},
{ 0, }
};
static struct pci_driver sercos3_pci_driver = {
.name = "sercos3",
.id_table = sercos3_pci_ids,
.probe = sercos3_pci_probe,
.remove = sercos3_pci_remove,
};
module_pci_driver(sercos3_pci_driver);
MODULE_DESCRIPTION("UIO driver for the Automata Sercos III PCI card");
MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>");
MODULE_LICENSE("GPL v2");