diff options
author | root <root@artemis.panaceas.org> | 2015-12-25 04:40:36 +0000 |
---|---|---|
committer | root <root@artemis.panaceas.org> | 2015-12-25 04:40:36 +0000 |
commit | 849369d6c66d3054688672f97d31fceb8e8230fb (patch) | |
tree | 6135abc790ca67dedbe07c39806591e70eda81ce /drivers/uio | |
download | linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.gz linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.bz2 linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.zip |
initial_commit
Diffstat (limited to 'drivers/uio')
-rw-r--r-- | drivers/uio/Kconfig | 114 | ||||
-rw-r--r-- | drivers/uio/Makefile | 9 | ||||
-rw-r--r-- | drivers/uio/uio.c | 909 | ||||
-rw-r--r-- | drivers/uio/uio_aec.c | 176 | ||||
-rw-r--r-- | drivers/uio/uio_cif.c | 153 | ||||
-rw-r--r-- | drivers/uio/uio_netx.c | 192 | ||||
-rw-r--r-- | drivers/uio/uio_pci_generic.c | 209 | ||||
-rw-r--r-- | drivers/uio/uio_pdrv.c | 121 | ||||
-rw-r--r-- | drivers/uio/uio_pdrv_genirq.c | 250 | ||||
-rw-r--r-- | drivers/uio/uio_pruss.c | 247 | ||||
-rw-r--r-- | drivers/uio/uio_sercos3.c | 244 |
11 files changed, 2624 insertions, 0 deletions
diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig new file mode 100644 index 00000000..6f3ea9bb --- /dev/null +++ b/drivers/uio/Kconfig @@ -0,0 +1,114 @@ +menuconfig UIO + tristate "Userspace I/O drivers" + depends on !S390 + 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 + tristate "Userspace I/O platform driver" + help + Generic platform driver for Userspace I/O devices. + + If you don't know what to do here, say N. + +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_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 + 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. + +endif diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile new file mode 100644 index 00000000..d4dd9a55 --- /dev/null +++ b/drivers/uio/Makefile @@ -0,0 +1,9 @@ +obj-$(CONFIG_UIO) += uio.o +obj-$(CONFIG_UIO_CIF) += uio_cif.o +obj-$(CONFIG_UIO_PDRV) += uio_pdrv.o +obj-$(CONFIG_UIO_PDRV_GENIRQ) += uio_pdrv_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 diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c new file mode 100644 index 00000000..d2efe823 --- /dev/null +++ b/drivers/uio/uio.c @@ -0,0 +1,909 @@ +/* + * 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) + +struct uio_device { + struct module *owner; + struct device *dev; + int minor; + atomic_t event; + struct fasync_struct *async_queue; + wait_queue_head_t wait; + int vma_count; + struct uio_info *info; + struct kobject *map_dir; + struct kobject *portio_dir; +}; + +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%lx\n", 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%lx\n", 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 show_name(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 ssize_t show_version(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 ssize_t show_event(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 struct device_attribute uio_class_attributes[] = { + __ATTR(name, S_IRUGO, show_name, NULL), + __ATTR(version, S_IRUGO, show_version, NULL), + __ATTR(event, S_IRUGO, show_event, NULL), + {} +}; + +/* UIO class infrastructure */ +static struct class uio_class = { + .name = "uio", + .dev_attrs = uio_class_attributes, +}; + +/* + * 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; + 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; + 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; + 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; + ret = kobject_uevent(&portio->kobj, KOBJ_ADD); + if (ret) + goto err_portio; + } + + return 0; + +err_portio: + for (pi--; pi >= 0; pi--) { + port = &idev->info->port[pi]; + portio = port->portio; + kobject_put(&portio->kobj); + } + kobject_put(idev->portio_dir); +err_map: + for (mi--; 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; + int id; + + mutex_lock(&minor_lock); + if (idr_pre_get(&uio_idr, GFP_KERNEL) == 0) + goto exit; + + retval = idr_get_new(&uio_idr, idev, &id); + if (retval < 0) { + if (retval == -EAGAIN) + retval = -ENOMEM; + goto exit; + } + if (id < UIO_MAX_DEVICES) { + idev->minor = id; + } else { + dev_err(idev->dev, "too many uio devices\n"); + retval = -EINVAL; + idr_remove(&uio_idr, id); + } +exit: + 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 void uio_vma_open(struct vm_area_struct *vma) +{ + struct uio_device *idev = vma->vm_private_data; + idev->vma_count++; +} + +static void uio_vma_close(struct vm_area_struct *vma) +{ + struct uio_device *idev = vma->vm_private_data; + idev->vma_count--; +} + +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; + + 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; + + if (idev->info->mem[mi].memtype == UIO_MEM_LOGICAL) + page = virt_to_page(idev->info->mem[mi].addr + offset); + else + page = vmalloc_to_page((void *)idev->info->mem[mi].addr + + offset); + get_page(page); + vmf->page = page; + return 0; +} + +static const struct vm_operations_struct uio_vm_ops = { + .open = uio_vma_open, + .close = uio_vma_close, + .fault = uio_vma_fault, +}; + +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); + if (mi < 0) + return -EINVAL; + + vma->vm_flags |= VM_IO | VM_RESERVED; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + return remap_pfn_range(vma, + vma->vm_start, + idev->info->mem[mi].addr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); +} + +static int uio_mmap_logical(struct vm_area_struct *vma) +{ + vma->vm_flags |= VM_RESERVED; + vma->vm_ops = &uio_vm_ops; + uio_vma_open(vma); + return 0; +} + +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->vm_end - vma->vm_start) >> PAGE_SHIFT; + 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; + result = 0; +out: + return result; +out_put: + kobject_put(&cdev->kobj); +out_unregister: + unregister_chrdev_region(uio_dev, UIO_MAX_DEVICES); + goto out; +} + +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 = kzalloc(sizeof(*idev), GFP_KERNEL); + if (!idev) { + ret = -ENOMEM; + goto err_kzalloc; + } + + idev->owner = owner; + idev->info = info; + init_waitqueue_head(&idev->wait); + atomic_set(&idev->event, 0); + + ret = uio_get_minor(idev); + if (ret) + goto err_get_minor; + + 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 = request_irq(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); +err_get_minor: + kfree(idev); +err_kzalloc: + 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); + + if (info->irq && (info->irq != UIO_IRQ_CUSTOM)) + free_irq(info->irq, idev); + + uio_dev_del_attributes(idev); + + device_destroy(&uio_class, MKDEV(uio_major, idev->minor)); + kfree(idev); + + 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"); diff --git a/drivers/uio/uio_aec.c b/drivers/uio/uio_aec.c new file mode 100644 index 00000000..72b22d44 --- /dev/null +++ b/drivers/uio/uio_aec.c @@ -0,0 +1,176 @@ +/* + * 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 __devinit 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); + pci_set_drvdata(pdev, NULL); + iounmap(info->priv); + + kfree(info); +} + +static struct pci_driver pci_driver = { + .name = "aectc", + .id_table = ids, + .probe = probe, + .remove = remove, +}; + +static int __init aectc_init(void) +{ + return pci_register_driver(&pci_driver); +} + +static void __exit aectc_exit(void) +{ + pci_unregister_driver(&pci_driver); +} + +MODULE_LICENSE("GPL"); + +module_init(aectc_init); +module_exit(aectc_exit); diff --git a/drivers/uio/uio_cif.c b/drivers/uio/uio_cif.c new file mode 100644 index 00000000..a84a4511 --- /dev/null +++ b/drivers/uio/uio_cif.c @@ -0,0 +1,153 @@ +/* + * 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 __devinit 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); + pci_set_drvdata(dev, NULL); + iounmap(info->mem[0].internal_addr); + + kfree (info); +} + +static struct pci_device_id hilscher_pci_ids[] __devinitdata = { + { + .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, +}; + +static int __init hilscher_init_module(void) +{ + return pci_register_driver(&hilscher_pci_driver); +} + +static void __exit hilscher_exit_module(void) +{ + pci_unregister_driver(&hilscher_pci_driver); +} + +module_init(hilscher_init_module); +module_exit(hilscher_exit_module); + +MODULE_DEVICE_TABLE(pci, hilscher_pci_ids); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Hans J. Koch, Benedikt Spranger"); diff --git a/drivers/uio/uio_netx.c b/drivers/uio/uio_netx.c new file mode 100644 index 00000000..a879fd57 --- /dev/null +++ b/drivers/uio/uio_netx.c @@ -0,0 +1,192 @@ +/* + * 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 __devinit 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); + pci_set_drvdata(dev, NULL); + 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, +}; + +static int __init netx_init_module(void) +{ + return pci_register_driver(&netx_pci_driver); +} + +static void __exit netx_exit_module(void) +{ + pci_unregister_driver(&netx_pci_driver); +} + +module_init(netx_init_module); +module_exit(netx_exit_module); + +MODULE_DEVICE_TABLE(pci, netx_pci_ids); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Hans J. Koch, Manuel Traut"); diff --git a/drivers/uio/uio_pci_generic.c b/drivers/uio/uio_pci_generic.c new file mode 100644 index 00000000..fc22e1e6 --- /dev/null +++ b/drivers/uio/uio_pci_generic.c @@ -0,0 +1,209 @@ +/* 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> +#include <linux/spinlock.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; + spinlock_t lock; /* guards command register accesses */ +}; + +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); + struct pci_dev *pdev = gdev->pdev; + irqreturn_t ret = IRQ_NONE; + u32 cmd_status_dword; + u16 origcmd, newcmd, status; + + /* We do a single dword read to retrieve both command and status. + * Document assumptions that make this possible. */ + BUILD_BUG_ON(PCI_COMMAND % 4); + BUILD_BUG_ON(PCI_COMMAND + 2 != PCI_STATUS); + + spin_lock_irq(&gdev->lock); + pci_block_user_cfg_access(pdev); + + /* Read both command and status registers in a single 32-bit operation. + * Note: we could cache the value for command and move the status read + * out of the lock if there was a way to get notified of user changes + * to command register through sysfs. Should be good for shared irqs. */ + pci_read_config_dword(pdev, PCI_COMMAND, &cmd_status_dword); + origcmd = cmd_status_dword; + status = cmd_status_dword >> 16; + + /* Check interrupt status register to see whether our device + * triggered the interrupt. */ + if (!(status & PCI_STATUS_INTERRUPT)) + goto done; + + /* We triggered the interrupt, disable it. */ + newcmd = origcmd | PCI_COMMAND_INTX_DISABLE; + if (newcmd != origcmd) + pci_write_config_word(pdev, PCI_COMMAND, newcmd); + + /* UIO core will signal the user process. */ + ret = IRQ_HANDLED; +done: + + pci_unblock_user_cfg_access(pdev); + spin_unlock_irq(&gdev->lock); + return ret; +} + +/* Verify that the device supports Interrupt Disable bit in command register, + * per PCI 2.3, by flipping this bit and reading it back: this bit was readonly + * in PCI 2.2. */ +static int __devinit verify_pci_2_3(struct pci_dev *pdev) +{ + u16 orig, new; + int err = 0; + + pci_block_user_cfg_access(pdev); + pci_read_config_word(pdev, PCI_COMMAND, &orig); + pci_write_config_word(pdev, PCI_COMMAND, + orig ^ PCI_COMMAND_INTX_DISABLE); + pci_read_config_word(pdev, PCI_COMMAND, &new); + /* There's no way to protect against + * hardware bugs or detect them reliably, but as long as we know + * what the value should be, let's go ahead and check it. */ + if ((new ^ orig) & ~PCI_COMMAND_INTX_DISABLE) { + err = -EBUSY; + dev_err(&pdev->dev, "Command changed from 0x%x to 0x%x: " + "driver or HW bug?\n", orig, new); + goto err; + } + if (!((new ^ orig) & PCI_COMMAND_INTX_DISABLE)) { + dev_warn(&pdev->dev, "Device does not support " + "disabling interrupts: unable to bind.\n"); + err = -ENODEV; + goto err; + } + /* Now restore the original value. */ + pci_write_config_word(pdev, PCI_COMMAND, orig); +err: + pci_unblock_user_cfg_access(pdev); + return err; +} + +static int __devinit 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; + } + + err = verify_pci_2_3(pdev); + if (err) + 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; + spin_lock_init(&gdev->lock); + + 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 driver = { + .name = "uio_pci_generic", + .id_table = NULL, /* only dynamic id's */ + .probe = probe, + .remove = remove, +}; + +static int __init init(void) +{ + pr_info(DRIVER_DESC " version: " DRIVER_VERSION "\n"); + return pci_register_driver(&driver); +} + +static void __exit cleanup(void) +{ + pci_unregister_driver(&driver); +} + +module_init(init); +module_exit(cleanup); + +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); diff --git a/drivers/uio/uio_pdrv.c b/drivers/uio/uio_pdrv.c new file mode 100644 index 00000000..7d3e469b --- /dev/null +++ b/drivers/uio/uio_pdrv.c @@ -0,0 +1,121 @@ +/* + * drivers/uio/uio_pdrv.c + * + * 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/stringify.h> +#include <linux/slab.h> + +#define DRIVER_NAME "uio_pdrv" + +struct uio_platdata { + struct uio_info *uioinfo; +}; + +static int uio_pdrv_probe(struct platform_device *pdev) +{ + struct uio_info *uioinfo = pdev->dev.platform_data; + struct uio_platdata *pdata; + struct uio_mem *uiomem; + int ret = -ENODEV; + int i; + + if (!uioinfo || !uioinfo->name || !uioinfo->version) { + dev_dbg(&pdev->dev, "%s: err_uioinfo\n", __func__); + goto err_uioinfo; + } + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + ret = -ENOMEM; + dev_dbg(&pdev->dev, "%s: err_alloc_pdata\n", __func__); + goto err_alloc_pdata; + } + + pdata->uioinfo = uioinfo; + + 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 = r->end - r->start + 1; + ++uiomem; + } + + while (uiomem < &uioinfo->mem[MAX_UIO_MAPS]) { + uiomem->size = 0; + ++uiomem; + } + + pdata->uioinfo->priv = pdata; + + ret = uio_register_device(&pdev->dev, pdata->uioinfo); + + if (ret) { + kfree(pdata); +err_alloc_pdata: +err_uioinfo: + return ret; + } + + platform_set_drvdata(pdev, pdata); + + return 0; +} + +static int uio_pdrv_remove(struct platform_device *pdev) +{ + struct uio_platdata *pdata = platform_get_drvdata(pdev); + + uio_unregister_device(pdata->uioinfo); + + kfree(pdata); + + return 0; +} + +static struct platform_driver uio_pdrv = { + .probe = uio_pdrv_probe, + .remove = uio_pdrv_remove, + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init uio_pdrv_init(void) +{ + return platform_driver_register(&uio_pdrv); +} + +static void __exit uio_pdrv_exit(void) +{ + platform_driver_unregister(&uio_pdrv); +} +module_init(uio_pdrv_init); +module_exit(uio_pdrv_exit); + +MODULE_AUTHOR("Uwe Kleine-Koenig"); +MODULE_DESCRIPTION("Userspace I/O platform driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/uio/uio_pdrv_genirq.c b/drivers/uio/uio_pdrv_genirq.c new file mode 100644 index 00000000..e669a2c6 --- /dev/null +++ b/drivers/uio/uio_pdrv_genirq.c @@ -0,0 +1,250 @@ +/* + * 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/interrupt.h> +#include <linux/stringify.h> +#include <linux/pm_runtime.h> +#include <linux/slab.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; +}; + +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. + */ + + if (!test_and_set_bit(0, &priv->flags)) + disable_irq_nosync(irq); + + 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. + */ + + 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_pdrv_genirq_probe(struct platform_device *pdev) +{ + struct uio_info *uioinfo = pdev->dev.platform_data; + struct uio_pdrv_genirq_platdata *priv; + struct uio_mem *uiomem; + int ret = -EINVAL; + int i; + + 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; + } + + priv->uioinfo = uioinfo; + spin_lock_init(&priv->lock); + priv->flags = 0; /* interrupt is enabled to begin with */ + priv->pdev = pdev; + + 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 = r->end - r->start + 1; + ++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"); + goto bad1; + } + + platform_set_drvdata(pdev, priv); + return 0; + bad1: + kfree(priv); + pm_runtime_disable(&pdev->dev); + bad0: + return ret; +} + +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->irq_flags = 0; + priv->uioinfo->handler = NULL; + priv->uioinfo->irqcontrol = NULL; + priv->uioinfo->priv = NULL; + + kfree(priv); + 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, +}; + +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, + }, +}; + +static int __init uio_pdrv_genirq_init(void) +{ + return platform_driver_register(&uio_pdrv_genirq); +} + +static void __exit uio_pdrv_genirq_exit(void) +{ + platform_driver_unregister(&uio_pdrv_genirq); +} + +module_init(uio_pdrv_genirq_init); +module_exit(uio_pdrv_genirq_exit); + +MODULE_AUTHOR("Magnus Damm"); +MODULE_DESCRIPTION("Userspace I/O platform driver with generic IRQ handling"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/uio/uio_pruss.c b/drivers/uio/uio_pruss.c new file mode 100644 index 00000000..e67b566e --- /dev/null +++ b/drivers/uio/uio_pruss.c @@ -0,0 +1,247 @@ +/* + * 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 <mach/sram.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; + void *sram_vaddr; + void *ddr_vaddr; + unsigned int hostirq_start; + unsigned int pintc_base; +}; + +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 platform_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->dev, extram_pool_sz, gdev->ddr_vaddr, + gdev->ddr_paddr); + } + if (gdev->sram_vaddr) + sram_free(gdev->sram_vaddr, sram_pool_sz); + kfree(gdev->info); + clk_put(gdev->pruss_clk); + kfree(gdev); +} + +static int __devinit pruss_probe(struct platform_device *dev) +{ + struct uio_info *p; + struct uio_pruss_dev *gdev; + struct resource *regs_prussio; + int ret = -ENODEV, cnt = 0, len; + struct uio_pruss_pdata *pdata = dev->dev.platform_data; + + 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->dev, "pruss"); + if (IS_ERR(gdev->pruss_clk)) { + dev_err(&dev->dev, "Failed to get clock\n"); + kfree(gdev->info); + kfree(gdev); + ret = PTR_ERR(gdev->pruss_clk); + return ret; + } else { + clk_enable(gdev->pruss_clk); + } + + regs_prussio = platform_get_resource(dev, IORESOURCE_MEM, 0); + if (!regs_prussio) { + dev_err(&dev->dev, "No PRUSS I/O resource specified\n"); + goto out_free; + } + + if (!regs_prussio->start) { + dev_err(&dev->dev, "Invalid memory resource\n"); + goto out_free; + } + + gdev->sram_vaddr = sram_alloc(sram_pool_sz, &(gdev->sram_paddr)); + if (!gdev->sram_vaddr) { + dev_err(&dev->dev, "Could not allocate SRAM pool\n"); + goto out_free; + } + + gdev->ddr_vaddr = dma_alloc_coherent(&dev->dev, extram_pool_sz, + &(gdev->ddr_paddr), GFP_KERNEL | GFP_DMA); + if (!gdev->ddr_vaddr) { + dev_err(&dev->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->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(dev, 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->dev, p); + if (ret < 0) + goto out_free; + } + + platform_set_drvdata(dev, gdev); + return 0; + +out_free: + pruss_cleanup(dev, gdev); + return ret; +} + +static int __devexit pruss_remove(struct platform_device *dev) +{ + struct uio_pruss_dev *gdev = platform_get_drvdata(dev); + + pruss_cleanup(dev, gdev); + platform_set_drvdata(dev, NULL); + return 0; +} + +static struct platform_driver pruss_driver = { + .probe = pruss_probe, + .remove = __devexit_p(pruss_remove), + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +static int __init pruss_init_module(void) +{ + return platform_driver_register(&pruss_driver); +} + +module_init(pruss_init_module); + +static void __exit pruss_exit_module(void) +{ + platform_driver_unregister(&pruss_driver); +} + +module_exit(pruss_exit_module); + +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DRV_VERSION); +MODULE_AUTHOR("Amit Chatterjee <amit.chatterjee@ti.com>"); +MODULE_AUTHOR("Pratheesh Gangadhar <pratheesh@ti.com>"); diff --git a/drivers/uio/uio_sercos3.c b/drivers/uio/uio_sercos3.c new file mode 100644 index 00000000..a187fa14 --- /dev/null +++ b/drivers/uio/uio_sercos3.c @@ -0,0 +1,244 @@ +/* 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 __devinit 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); + pci_set_drvdata(dev, NULL); + 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[] __devinitdata = { + { + .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, +}; + +static int __init sercos3_init_module(void) +{ + return pci_register_driver(&sercos3_pci_driver); +} + +static void __exit sercos3_exit_module(void) +{ + pci_unregister_driver(&sercos3_pci_driver); +} + +module_init(sercos3_init_module); +module_exit(sercos3_exit_module); + +MODULE_DESCRIPTION("UIO driver for the Automata Sercos III PCI card"); +MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>"); +MODULE_LICENSE("GPL v2"); |