mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-09-08 09:08:05 -04:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
138
drivers/uio/Kconfig
Normal file
138
drivers/uio/Kconfig
Normal 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
10
drivers/uio/Makefile
Normal 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
892
drivers/uio/uio.c
Normal 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
163
drivers/uio/uio_aec.c
Normal 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
140
drivers/uio/uio_cif.c
Normal 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");
|
357
drivers/uio/uio_dmem_genirq.c
Normal file
357
drivers/uio/uio_dmem_genirq.c
Normal 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
246
drivers/uio/uio_mf624.c
Normal 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
179
drivers/uio/uio_netx.c
Normal 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");
|
127
drivers/uio/uio_pci_generic.c
Normal file
127
drivers/uio/uio_pci_generic.c
Normal 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);
|
280
drivers/uio/uio_pdrv_genirq.c
Normal file
280
drivers/uio/uio_pdrv_genirq.c
Normal 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
243
drivers/uio/uio_pruss.c
Normal 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
231
drivers/uio/uio_sercos3.c
Normal 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");
|
Loading…
Add table
Add a link
Reference in a new issue