nxdrvlinux/uio_netx/uio_netx.c

933 lines
27 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* UIO Hilscher netX card driver
*
* (C) 2007 Hans J. Koch <hjk@linutronix.de>
* (C) 2013 Sebastian Doell <sdoell@hilscher.com> Added DMA Support
* (C) 2014 Sebastian Doell <sdoell@hilscher.com> Added support for memory mapped
* devices given by user via command line (e.g. ISA)
* (C) 2017 Sebastian Doell <sdoell@hilscher.com> Added device-tree support
* (C) 2019 Sebastian Doell <sdoell@hilscher.com> Added netx4000 support
*
*/
#include <linux/device.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/uio_driver.h>
#include <linux/dma-mapping.h>
#include <linux/io.h>
#include <linux/version.h>
#include <asm/cacheflush.h>
#include <linux/miscdevice.h>
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/of_address.h>
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0))
#define ioremap_nocache ioremap
#endif
#define MAX_USER_CARDS 10
#define UIO_NETX_VERSION "2.2.0"
int addr_cnt = 0;
unsigned long custom_dpm_len[MAX_USER_CARDS] = {0};
int len_cnt = 0;
int custom_irq[MAX_USER_CARDS] = {0};
int irq_cnt = 0;
#ifdef CONFIG_64BIT
phys_addr_t custom_dpm_addr[MAX_USER_CARDS] = {0};
module_param_array(custom_dpm_addr, ullong, &addr_cnt, S_IRUGO);
#else
unsigned long custom_dpm_addr[MAX_USER_CARDS] = {0};
module_param_array(custom_dpm_addr, ulong, &addr_cnt, S_IRUGO);
#endif
MODULE_PARM_DESC(custom_dpm_addr, "Start address of DPM (array)");
module_param_array(custom_dpm_len, ulong, &len_cnt, S_IRUGO);
MODULE_PARM_DESC(custom_dpm_len, "Length of DPM (array)");
module_param_array(custom_irq, int, &irq_cnt, S_IRUGO);
MODULE_PARM_DESC(custom_irq, "IRQ number (array)");
#ifdef DMA_SUPPORT
#define DMA_BUFFER_COUNT 1
#define DMA_BUFFER_SIZE 8*8*1024
unsigned long dma_disable = 0;
unsigned long dma_buffer_count = DMA_BUFFER_COUNT;
unsigned long dma_buffer_size = DMA_BUFFER_SIZE;
module_param(dma_disable, ulong, S_IRUGO);
MODULE_PARM_DESC(dma_disable, "Disable DMA buffer allocation.");
module_param(dma_buffer_count, ulong, S_IRUGO);
MODULE_PARM_DESC(dma_buffer_count, "Number of DMA-buffers to use.");
module_param(dma_buffer_size, ulong, S_IRUGO);
MODULE_PARM_DESC(dma_buffer_size, "Size of a DMA-buffer.");
#endif
#define PCI_VENDOR_ID_HILSCHER 0x15CF
#define PCI_DEVICE_ID_HILSCHER_NETX 0x0000
#define PCI_DEVICE_ID_HILSCHER_NETPLC 0x0010
#define PCI_DEVICE_ID_HILSCHER_NETJACK 0x0020
#define PCI_DEVICE_ID_HILSCHER_NETX4000 0x4000
#define PCI_SUBDEVICE_ID_NXSB_PCA 0x3235
#define PCI_SUBDEVICE_ID_NXPCA 0x3335
#define PCI_SUBDEVICE_ID_NETPLC_RAM 0x0000
#define PCI_SUBDEVICE_ID_NETPLC_FLASH 0x0001
#define PCI_SUBDEVICE_ID_NETJACK_RAM 0x0000
#define PCI_SUBDEVICE_ID_NETJACK_FLASH 0x0001
#define DPM_HOST_INT_EN0 0xfff0
#define DPM_HOST_INT_STAT0 0xffe0
#define PLX_GPIO_OFFSET 0x15
#define PLX_TIMING_OFFSET 0x0a
#define DPM_HOST_INT_MASK 0xe600ffff
#define DPM_HOST_INT_GLOBAL_EN 0x80000000
#define PLX_GPIO_DATA0_MASK 0x00000004
#define PLX_GPIO_DATA1_MASK 0x00000020
#define NX_PCA_PCI_8_BIT_DPM_MODE 0x5431F962
#define NX_PCA_PCI_16_BIT_DPM_MODE 0x4073F8E2
#define NX_PCA_PCI_32_BIT_DPM_MODE 0x40824122
#define NETX_DPM_SIZE_64K 0x10000
/* number of bar */
#define DPM_BAR 0 /* points to the DPM -> netX, netPLC, netJACK */
#define EXT_MEM_BAR 1 /* points to the optional extended memory */
#define PLX_DPM_BAR 2 /* points to the DPM -> netXPLX */
#define PXA_PLX_BAR 0 /* timing config register */
/* index of uio_info structure's memory array */
#define DPM_INDEX 0 /* first mapping describes DPM */
#define EXT_MEM_INDEX 1 /* second mapping describes extended memory */
#define DPM_MEM_NAME "dpm"
#define EXT_MEM_NAME "extmem"
#define DMA_MEM_NAME "dma"
DEFINE_MUTEX(custom_list_lock);
uint8_t card_count = 0;
struct pxa_dev_info {
uint32_t __iomem *plx;
uint8_t dpm_mode;
uint32_t plx_timing;
};
struct uio_netx_priv {
int32_t dmacount;
int32_t memcount;
struct pxa_dev_info *pxa_info;
int8_t no_irq_stat;
};
#define NETX_NAME_LEN_MAX 64
struct netx_custom_dev {
struct list_head list;
struct miscdevice* misc;
struct uio_info* info;
struct device* dev;
#ifdef DMA_SUPPORT
int dma_enable;
#endif
char device_name[NETX_NAME_LEN_MAX];
phys_addr_t* dpm_addr;
unsigned long* dpm_len;
int* irq;
};
static LIST_HEAD(custom_list);
static int netx_enable_irq(struct uio_info *dev_info, s32 irq_on)
{
if (dev_info->irq == 0)
return -EPERM; /* Not supported! -> there is no interrupt registered */
return 0;
}
static irqreturn_t netx_handler(int irq, struct uio_info *dev_info)
{
if(((struct uio_netx_priv*)dev_info->priv)->pxa_info != NULL)
{
/* This is a PLX device and cannot produce an IRQ */
return IRQ_NONE;
} else
{
/* check if the device provides a global interrupt status reg */
if (!((struct uio_netx_priv*)dev_info->priv)->no_irq_stat) {
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_pxa_set_plx_timing(struct uio_info *info)
{
struct uio_netx_priv *priv = (struct uio_netx_priv *) info->priv;
uint32_t __iomem *plx_timing;
if (!priv->pxa_info)
return -ENODEV;
plx_timing = priv->pxa_info->plx + PLX_TIMING_OFFSET;
*plx_timing = priv->pxa_info->plx_timing;
return 0;
}
static int netx_pxa_get_plx_timing(struct uio_info *info)
{
struct uio_netx_priv *priv = (struct uio_netx_priv *) info->priv;
if (!priv->pxa_info)
return -ENODEV;
switch (priv->pxa_info->dpm_mode) {
case 8:
priv->pxa_info->plx_timing = NX_PCA_PCI_8_BIT_DPM_MODE;
break;
case 16:
priv->pxa_info->plx_timing = NX_PCA_PCI_16_BIT_DPM_MODE;
break;
case 32:
priv->pxa_info->plx_timing = NX_PCA_PCI_32_BIT_DPM_MODE;
break;
default:
return -EINVAL;
}
return 0;
}
static int netx_pxa_get_dpm_mode(struct uio_info *info)
{
struct uio_netx_priv *priv = (struct uio_netx_priv *) info->priv;
uint32_t __iomem *plx_gpio;
if (!priv->pxa_info)
return -ENODEV;
plx_gpio = priv->pxa_info->plx + PLX_GPIO_OFFSET;
if ((*plx_gpio & PLX_GPIO_DATA0_MASK) &&
~(*plx_gpio & PLX_GPIO_DATA1_MASK))
priv->pxa_info->dpm_mode = 8;
else if (~(*plx_gpio & PLX_GPIO_DATA0_MASK) &&
(*plx_gpio & PLX_GPIO_DATA1_MASK))
priv->pxa_info->dpm_mode = 32;
else if (~(*plx_gpio & PLX_GPIO_DATA0_MASK) &&
~(*plx_gpio & PLX_GPIO_DATA1_MASK))
priv->pxa_info->dpm_mode = 16;
else
return -EINVAL;
return 0;
}
#ifdef DMA_SUPPORT
int create_dma_buffer(struct device *dev, struct uio_info *info, struct uio_mem *dma_mem)
{
void __iomem *addr;
dma_addr_t busaddr;
/* Allocate DMA-capable buffer */
addr = dma_alloc_coherent(dev, dma_buffer_size,
&busaddr,
GFP_KERNEL);
if (!addr) {
dev_warn(dev, "Unable to allocate requested DMA-capable"
" block of size 0x%lx during mmap in uio.\n",
dma_buffer_size);
return -ENOMEM;
}
/* Store the physical address and index as the
* first two long words for userspace access */
memset(addr ,0 ,dma_buffer_size);
dma_mem->addr = busaddr;
dma_mem->internal_addr = addr;
dma_mem->size = dma_buffer_size;
dma_mem->name = DMA_MEM_NAME;
dma_mem->memtype = UIO_MEM_PHYS;
return 0;
}
int release_dma_mem(struct device *dev, struct uio_info *info)
{
struct uio_netx_priv *priv = info->priv;
while(priv->dmacount-->0) {
priv->memcount--;
dma_free_coherent(dev,
info->mem[priv->memcount].size,
(void*)(info->mem[priv->memcount].internal_addr),
(dma_addr_t) info->mem[priv->memcount].addr);
info->mem[priv->memcount].addr = 0;
info->mem[priv->memcount].size = 0;
info->mem[priv->memcount].internal_addr = 0;
}
return 0;
}
static int add_dma(struct device *dev, struct uio_info *info)
{
struct uio_netx_priv *priv = info->priv;
int i = 0;
int ret = 0;
if (MAX_UIO_MAPS<(priv->memcount+dma_buffer_count)) {
dev_info(dev, "Base uio driver does not serve enough memory\n"
"regions for dma allocation (see MAX_UIO_MAPS)!\n");
return -ENOMEM;
}
for (;i<dma_buffer_count;i++) {
if ((ret = create_dma_buffer(dev, info, &info->mem[i+priv->memcount])))
goto err_dma;
dev_info(dev, "DMA buffer allocated (addr/size:0x%llX/0x%lX)\n",
info->mem[i+priv->memcount].addr,
(long unsigned int)info->mem[i+priv->memcount].size);
priv->dmacount++;
}
priv->memcount+=dma_buffer_count;
return 0;
err_dma:
release_dma_mem(dev, info);
return ret;
}
#endif
#ifndef __devinit
#define __devinit
#endif
static int __devinit netx_pci_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
struct uio_info *info;
int bar;
info = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->version = UIO_NETX_VERSION;
if (!(info->priv = (struct uio_netx_priv *) kzalloc(sizeof(struct uio_netx_priv), GFP_KERNEL)))
goto out_priv;
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 = DPM_BAR;
info->name = "netx";
break;
case PCI_DEVICE_ID_HILSCHER_NETPLC:
bar = DPM_BAR;
info->name = "netplc";
break;
case PCI_DEVICE_ID_HILSCHER_NETJACK:
bar = DPM_BAR;
info->name = "netjack";
break;
case PCI_DEVICE_ID_HILSCHER_NETX4000:
bar = DPM_BAR;
info->name = "netx";
break;
default:
bar = PLX_DPM_BAR;
info->name = "netx_plx";
}
/* BAR 0 or 2 points to the card's dual port memory */
info->mem[DPM_INDEX].addr = pci_resource_start(dev, bar);
if (!info->mem[DPM_INDEX].addr) {
dev_err( &dev->dev, "Error retrieving the memory address of the device!\n");
goto out_release;
}
info->mem[DPM_INDEX].internal_addr = ioremap_nocache(
pci_resource_start(dev, bar),
pci_resource_len(dev, bar));
if (!info->mem[DPM_INDEX].internal_addr) {
dev_err( &dev->dev, "Error mapping the DPM of the device!\n");
goto out_release;
}
dev_info(&dev->dev, "DPM at 0x%llX\n", info->mem[DPM_INDEX].addr);
info->mem[DPM_INDEX].size = pci_resource_len(dev, bar);
info->mem[DPM_INDEX].memtype = UIO_MEM_PHYS;
info->mem[DPM_INDEX].name = DPM_MEM_NAME;
((struct uio_netx_priv*)(info->priv))->memcount = 1;
/* map extended mem (BAR 1 points to the extended memory) */
info->mem[EXT_MEM_INDEX].addr = pci_resource_start(dev, EXT_MEM_BAR);
/* extended memory is optional, so don't care if it is not present */
if (info->mem[EXT_MEM_INDEX].addr) {
info->mem[EXT_MEM_INDEX].internal_addr = ioremap_nocache(
pci_resource_start(dev, EXT_MEM_BAR),
pci_resource_len(dev, EXT_MEM_BAR));
if (!info->mem[EXT_MEM_INDEX].internal_addr)
goto out_unmap;
dev_info(&dev->dev, "extended memory at 0x%llX\n", info->mem[EXT_MEM_INDEX].addr);
info->mem[EXT_MEM_INDEX].size = pci_resource_len(dev, EXT_MEM_BAR);
info->mem[EXT_MEM_INDEX].memtype = UIO_MEM_PHYS;
info->mem[EXT_MEM_INDEX].name = EXT_MEM_NAME;
((struct uio_netx_priv*)(info->priv))->memcount++;
}
info->irq = dev->irq;
# if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0))
info->irq_flags = IRQF_DISABLED | IRQF_SHARED;
# else
info->irq_flags = IRQF_SHARED;
# endif
info->handler = netx_handler;
info->irqcontrol = netx_enable_irq;
if ((id->device == PCI_DEVICE_ID_HILSCHER_NETX) ||
(id->device == PCI_DEVICE_ID_HILSCHER_NETPLC) ||
(id->device == PCI_DEVICE_ID_HILSCHER_NETJACK) ||
(id->device == PCI_DEVICE_ID_HILSCHER_NETX4000)) {
/* make sure all interrupts are disabled */
iowrite32(0, info->mem[DPM_INDEX].internal_addr + DPM_HOST_INT_EN0);
((struct uio_netx_priv*)(info->priv))->pxa_info = NULL;
} else if (id->subdevice == PCI_SUBDEVICE_ID_NXPCA) {
/* map PLX registers */
struct pxa_dev_info *pxa_info = (struct pxa_dev_info *)
kzalloc(sizeof(struct pxa_dev_info), GFP_KERNEL);
if (!pxa_info)
goto out_unmap;
((struct uio_netx_priv*)(info->priv))->pxa_info = pxa_info;
/* set PXA PLX Timings */
pxa_info->plx = ioremap_nocache(
pci_resource_start(dev, PXA_PLX_BAR),
pci_resource_len(dev, PXA_PLX_BAR));
if (!pxa_info->plx)
goto out_unmap;
if (netx_pxa_get_dpm_mode(info))
goto out_unmap_plx;
if (netx_pxa_get_plx_timing(info))
goto out_unmap_plx;
if (netx_pxa_set_plx_timing(info))
goto out_unmap_plx;
} else {
struct pxa_dev_info *pxa_info = (struct pxa_dev_info *)
kzalloc(sizeof(struct pxa_dev_info), GFP_KERNEL);
if (!pxa_info)
goto out_free_pxa;
pxa_info->plx = NULL;
pxa_info->plx_timing = 0;
pxa_info->dpm_mode = 0;
((struct uio_netx_priv*)info->priv)->pxa_info = pxa_info;
}
#ifdef DMA_SUPPORT
if ((!dma_disable) && (add_dma(&dev->dev, info)))
dev_warn( &dev->dev, "Error reserving memory for dma!\n");
#endif
if (uio_register_device(&dev->dev, info)) {
if (id->subdevice != PCI_SUBDEVICE_ID_NXPCA)
goto out_unmap;
else
goto out_unmap_plx;
}
pci_set_drvdata(dev, info);
if ((id->device == PCI_DEVICE_ID_HILSCHER_NETX) || (id->device == PCI_DEVICE_ID_HILSCHER_NETX4000))
dev_info(&dev->dev,
"registered CifX card\n");
else if (id->device == PCI_DEVICE_ID_HILSCHER_NETPLC)
dev_info(&dev->dev,
"registered netPLC card\n");
else if (id->device == PCI_DEVICE_ID_HILSCHER_NETJACK)
dev_info(&dev->dev, "registered netJACK card\n");
else if (id->subdevice == PCI_SUBDEVICE_ID_NXSB_PCA)
dev_info(&dev->dev,
"registered NXSB-PCA adapter card\n");
else {
struct pxa_dev_info *pxa_info = (struct pxa_dev_info *)
((struct uio_netx_priv*)info->priv)->pxa_info;
dev_info(&dev->dev,
"registered NXPCA-PCI adapter card in %d bit mode\n",
pxa_info->dpm_mode);
}
return 0;
out_unmap_plx:
iounmap(((struct uio_netx_priv*)(info->priv))->pxa_info->plx);
out_free_pxa:
kfree(((struct uio_netx_priv*)info->priv)->pxa_info);
out_unmap:
#ifdef DMA_SUPPORT
release_dma_mem(&dev->dev, info);
#endif
iounmap(info->mem[DPM_INDEX].internal_addr);
if (info->mem[EXT_MEM_INDEX].internal_addr)
iounmap(info->mem[EXT_MEM_INDEX].internal_addr);
out_release:
pci_release_regions(dev);
out_disable:
pci_disable_device(dev);
out_priv:
kfree(info->priv);
out_free:
kfree(info);
return -ENODEV;
}
static void netx_pci_remove(struct pci_dev *dev)
{
struct uio_info *info = pci_get_drvdata(dev);
struct pxa_dev_info *pxa_info = ((struct uio_netx_priv*)info->priv)->pxa_info;
if (pxa_info) {
if ( pxa_info->plx)
iounmap(pxa_info->plx);
kfree(pxa_info);
} else {
/* Disable all interrupts (interrupt is only enabled for none-PCA devices) */
iowrite32(0, info->mem[DPM_INDEX].internal_addr + DPM_HOST_INT_EN0);
}
uio_unregister_device(info);
#ifdef DMA_SUPPORT
release_dma_mem(&dev->dev, info);
#endif
pci_release_regions(dev);
pci_disable_device(dev);
pci_set_drvdata(dev, NULL);
iounmap(info->mem[DPM_INDEX].internal_addr);
if (info->mem[EXT_MEM_INDEX].internal_addr)
iounmap(info->mem[EXT_MEM_INDEX].internal_addr);
kfree(info->priv);
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_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,
},
{
.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_HILSCHER,
.device = PCI_DEVICE_ID_HILSCHER_NETJACK,
.subvendor = PCI_VENDOR_ID_HILSCHER,
.subdevice = PCI_SUBDEVICE_ID_NETJACK_RAM,
},
{
.vendor = PCI_VENDOR_ID_HILSCHER,
.device = PCI_DEVICE_ID_HILSCHER_NETJACK,
.subvendor = PCI_VENDOR_ID_HILSCHER,
.subdevice = PCI_SUBDEVICE_ID_NETJACK_FLASH,
},
{
.vendor = PCI_VENDOR_ID_HILSCHER,
.device = PCI_DEVICE_ID_HILSCHER_NETX4000,
.subvendor = 0,
.subdevice = 0,
},
{ 0, }
};
/* publish PCI ids, to provide automatic load for known PCI cards */
MODULE_DEVICE_TABLE(pci, netx_pci_ids);
static struct pci_driver netx_pci_driver = {
.name = "netx",
.id_table = netx_pci_ids,
.probe = netx_pci_probe,
.remove = netx_pci_remove,
};
static int misc_counter = 0;
static int create_misc_device(struct netx_custom_dev* custom)
{
int ret = 0;
if (custom->dev == NULL) {
char* name = kzalloc(16, GFP_KERNEL);
if (name == NULL) {
printk("uio_netx - custom card(%d): "\
"Error allocating memory for custom netx device!\n", card_count);
return -ENOMEM;
}
if ((custom->misc = kzalloc(sizeof(struct miscdevice), GFP_KERNEL)) != NULL) {
sprintf(name, "netx_custom%d", misc_counter++);
custom->misc->minor = MISC_DYNAMIC_MINOR;
custom->misc->name = name;
if ((ret = misc_register( custom->misc))) {
kfree( custom->misc->name);
kfree( custom->misc);
printk("uio_netx - custom card(%d): "\
"Error creating misc-device (ret=%d)!\n", card_count, ret);
return -ENOMEM;
}
custom->dev = custom->misc->this_device;
#ifdef DMA_SUPPORT
/* this device is passed via command line so use command line DMA settings */
custom->dma_enable = !(dma_disable);
#endif
} else {
printk("uio_netx - custom card(%d): "\
"Error allocating memory for custom netx device!\n", card_count);
kfree(name);
return -ENOMEM;
}
}
return 0;
}
void delete_misc_device(struct miscdevice* misc)
{
if (misc) {
misc_deregister( misc);
kfree( misc->name);
kfree( misc);
}
}
void free_netx_custom_dev(struct netx_custom_dev* custom)
{
if (custom) {
if (custom->misc) {
delete_misc_device(custom->misc);
}
if (custom->dpm_addr)
kfree(custom->dpm_addr);
if (custom->dpm_len)
kfree(custom->dpm_len);
if (custom->irq)
kfree(custom->irq);
kfree(custom);
}
}
struct netx_custom_dev* alloc_netx_custom_dev(struct device* dev){
struct netx_custom_dev* custom = kzalloc(sizeof(struct netx_custom_dev), GFP_KERNEL);
if (custom == NULL) {
printk("uio_netx - custom card: "\
"Error allocating memory for custom netx device!\n");
return NULL;
} else {
sprintf( custom->device_name, "netx_custom");
if (dev == NULL) {
if (create_misc_device(custom))
kfree(custom);
} else {
custom->dev = dev;
}
}
return custom;
}
static int map_custom_card( struct netx_custom_dev* custom, int no_of_maps)
{
int ret,i = 0;
struct uio_info* info = NULL;
if (no_of_maps == 0)
return -EINVAL;
info = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
if ((!info) || (!(info->priv = kzalloc(sizeof(struct uio_netx_priv), GFP_KERNEL)))) {
dev_info(custom->dev, "uio_netx - custom card(%d): "\
"Error allocating memory for custom netx device!\n", card_count);
if (info) kfree( info);
return -ENOMEM;
}
info->version = UIO_NETX_VERSION;
info->name = custom->device_name;
ret = 0;
for (i=0;(i<no_of_maps) && (ret == 0);i++) {
info->mem[DPM_INDEX+i].addr = custom->dpm_addr[i];
info->mem[DPM_INDEX+i].internal_addr = ioremap_nocache(custom->dpm_addr[i], custom->dpm_len[i]);
info->mem[DPM_INDEX+i].size = custom->dpm_len[i];
info->mem[DPM_INDEX+i].memtype = UIO_MEM_PHYS;
info->mem[DPM_INDEX+i].name = (i == 0) ? DPM_MEM_NAME : EXT_MEM_NAME;
((struct uio_netx_priv*)(info->priv))->memcount++;
if (info->mem[DPM_INDEX+i].internal_addr == NULL)
ret = -1;
}
/* check if everthing is mapped */
if (ret != 0) {
while(--i>0) {
iounmap(custom->info->mem[i].internal_addr);
}
kfree( info->priv);
kfree( info);
return -ENOMEM;
}
/* check if device provides global interrupt status reg */
if (custom->dpm_len[0]<NETX_DPM_SIZE_64K)
((struct uio_netx_priv*)(info->priv))->no_irq_stat = 1;
info->irq = custom->irq[0];
# if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 1, 0))
info->irq_flags = IRQF_DISABLED;
# else
info->irq_flags = 0;
# endif
info->handler = netx_handler;
info->irqcontrol = netx_enable_irq;
#ifdef DMA_SUPPORT
if ((custom->dma_enable) && (add_dma( custom->dev, info)))
dev_info(custom->dev, "uio_netx - custom card(%d): "\
"Error reserving memory for DMA!\n", card_count);
#endif
if ((ret = uio_register_device( custom->dev, info))) {
dev_info(custom->dev, "uio_netx - custom card(%d): "\
"Error registering custom netx device (ret=%d)!\n", card_count, ret);
kfree( info->priv);
kfree( info);
return -ENOMEM;
} else {
custom->info = info;
mutex_lock( &custom_list_lock);
list_add(&custom->list, &custom_list);
card_count++;
mutex_unlock( &custom_list_lock);
dev_info(custom->dev, "uio_netx - custom card(%d): "\
"Successfuly registered custom netx device (DPM at 0x%llX)!\n", card_count-1, custom->dpm_addr[0]);
}
return 0;
}
void unmap_custom_cards(struct device* dev)
{
struct list_head *pos;
struct list_head *next;
mutex_lock( &custom_list_lock);
list_for_each_safe( pos, next, &custom_list) {
struct netx_custom_dev* custom = list_entry( pos, struct netx_custom_dev, list);
/* in case dev is given delete only this element */
if (dev != NULL) {
if (custom->dev != dev) {
custom = NULL;
}
}
if (custom != NULL) {
struct uio_netx_priv* priv = (struct uio_netx_priv*)custom->info->priv;
uio_unregister_device(custom->info);
while(priv->memcount-->0) {
if (NULL != custom->info->mem[priv->memcount].internal_addr)
iounmap(custom->info->mem[priv->memcount].internal_addr);
}
#ifdef DMA_SUPPORT
release_dma_mem(custom->dev, custom->info);
#endif
kfree( custom->info->priv);
kfree( custom->info);
list_del( pos);
card_count--;
free_netx_custom_dev( custom);
/* in case dev is given we are done here */
if (dev != NULL) {
break;
}
}
}
mutex_unlock( &custom_list_lock);
}
#ifdef CONFIG_OF
int get_dt_parameter(struct platform_device *pd, struct netx_custom_dev* custom)
{
const void* ptr = NULL;
struct resource res;
int mappings = 0;
int ret = 0;
while ((ret = of_address_to_resource(custom->dev->of_node, mappings, &res)) == 0) {
mappings++;
}
dev_info( custom->dev, "uio_netx - get_dt_parameter: found %d mappings!\n",mappings);
if (mappings == 0)
return -ENODATA;
custom->dpm_addr = kzalloc((mappings)*sizeof(phys_addr_t), GFP_KERNEL);
custom->dpm_len = kzalloc((mappings)*sizeof(unsigned long), GFP_KERNEL);
custom->irq = kzalloc((mappings)*sizeof(int), GFP_KERNEL);
if (!(custom->dpm_addr) || !(custom->dpm_len) || !(custom->irq)) {
dev_err( custom->dev, "uio_netx - get_dt_parameter: failed to allocate memory for device mapping-parameter!\n");
return -ENOMEM;
}
while ((mappings>0) && (ret = of_address_to_resource(custom->dev->of_node, --mappings, &res)) == 0) {
custom->dpm_addr[mappings] = res.start;
custom->dpm_len[mappings] = resource_size(&res);
custom->irq[mappings] = 0;
}
/* set irq information */
custom->irq[0] = platform_get_irq_byname(pd, "card");
/* build device name (/sys/class/uio[x]/name) */
if (NULL != (ptr = of_get_property(custom->dev->of_node, "startuptype", NULL))) {
snprintf( custom->device_name, NETX_NAME_LEN_MAX, "netx_custom,%s,", (char*)ptr);
} else {
sprintf( custom->device_name, "netx_custom,-,");
}
if (NULL != (ptr = of_get_property(custom->dev->of_node, "alias", NULL))) {
snprintf( custom->device_name+(strlen(custom->device_name)),NETX_NAME_LEN_MAX-strlen(custom->device_name), "%s", (char*)ptr);
} else {
snprintf( custom->device_name+(strlen(custom->device_name)),NETX_NAME_LEN_MAX-strlen(custom->device_name), "-");
}
#if defined DMA_SUPPORT
ptr = of_get_property(custom->dev->of_node, "dma", NULL);
custom->dma_enable = !!!(*(uint32_t*)ptr) ? 1 : 0;
#endif
return mappings;
}
static int __devinit netx_dt_probe(struct platform_device *pd)
{
int ret = -ENOMEM;
struct netx_custom_dev* custom = alloc_netx_custom_dev(&pd->dev);
if (custom != NULL) {
/* try to get required information */
if ((ret = get_dt_parameter(pd, custom))>0) {
dev_info( custom->dev, "uio_netx - netx_dt_probe: device DPM @0x%llX ...+0x%lX (@irq=%d)\n", custom->dpm_addr[0], (unsigned int)custom->dpm_len[0], custom->irq[0]);
ret = map_custom_card( custom, ret);
} else {
dev_err( custom->dev, "uio_netx - netx_dt_probe: failed to request resources (%d)\n", ret);
}
if (ret<0)
free_netx_custom_dev(custom);
}
return ret;
}
static int netx_dt_remove(struct platform_device *pd)
{
unmap_custom_cards( &pd->dev);
return 0;
}
/****************************************************
*** netX0: netX@f8034000 {
*** status = "ok";
*** compatible = "hilscher,uio-netx";
*** reg = <0xf8034000 0x10000>; // can be multiple (1. DPM 2... extended memory)
*** interrupt-names = "card"; // only "card" supported
*** interrupts = <168 IRQ_TYPE_LEVEL_HIGH>;
*** dma = <1>; // enable / disable
*** startuptype = "auto"; // specifies startup behaviour: flash,ram,auto,donttouch
*** alias = "custom-device"; // device name
*** }
***************************************************/
static struct of_device_id const uio_netx_of_match[] __refconst = {
{ .compatible = "hilscher,uio-netx", },
{}
};
MODULE_DEVICE_TABLE(of, uio_netx_of_match);
static struct platform_driver uio_netx_platform_driver = {
.probe = netx_dt_probe,
.remove = netx_dt_remove,
.driver = {
.name = "uio-netx",
.owner = THIS_MODULE,
.of_match_table = uio_netx_of_match,
},
};
#endif //CONFIG_OF
static int __init netx_init_module(void)
{
INIT_LIST_HEAD(&custom_list);
mutex_init( &custom_list_lock);
/* parameter are given via command line */
if (addr_cnt) {/* custom card definition given via command line (e.g. ISA) */
if ((addr_cnt == len_cnt) && (addr_cnt == irq_cnt)) {
int max = addr_cnt;
while(addr_cnt>0) {
struct netx_custom_dev* custom = alloc_netx_custom_dev(NULL);
if (custom != NULL) {
if ((custom->dpm_addr = kzalloc(sizeof(phys_addr_t),GFP_KERNEL)))
*custom->dpm_addr = custom_dpm_addr[max - addr_cnt];
if ((custom->dpm_len = kzalloc(sizeof(unsigned long),GFP_KERNEL)))
*custom->dpm_len = custom_dpm_len[max - addr_cnt];
if ((custom->irq = kzalloc(sizeof(int),GFP_KERNEL)))
*custom->irq = custom_irq[max - addr_cnt];
if (map_custom_card( custom, 1)) {
/* failure during mapping custom device */
free_netx_custom_dev(custom);
}
}
addr_cnt--;
}
} else {
printk("uio_netx - Error registering passed user card. Invalid number of arguments!");
}
}
#ifdef CONFIG_OF
/* register driver for device tree */
platform_driver_register(&uio_netx_platform_driver);
#endif
/* and pci */
return pci_register_driver(&netx_pci_driver);
}
static void __exit netx_exit_module(void)
{
pci_unregister_driver(&netx_pci_driver);
#ifdef CONFIG_OF
platform_driver_unregister(&uio_netx_platform_driver);
#endif
unmap_custom_cards(NULL);
}
module_init(netx_init_module);
module_exit(netx_exit_module);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Hans J. Koch, Manuel Traut, Sebastian Doell");
MODULE_DESCRIPTION("Device driver for netX hardware\n\t\tHilscher Gesellschaft fuer Systemautomation mbH");