/* $ZEL: sis1100_autoconf_linux.c,v 1.5 2004/05/27 23:10:17 wuestner Exp $ */

/*
 * Copyright (c) 2001-2004
 * 	Matthias Drochner, Peter Wuestner.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "sis1100_sc.h"

int sis1100_major = -1;

struct pci_device_id sis1100_table[]={
    {
    PCI_VENDOR_FZJZEL, PCI_PRODUCT_FZJZEL_GIGALINK,
    PCI_ANY_ID, PCI_ANY_ID,
    0, 0,
    0
    },
    { 0 }
};

MODULE_AUTHOR("Peter Wuestner <P.Wuestner@fz-juelich.de>");
MODULE_DESCRIPTION("SIS1100 PCI-VME link/interface (http://zelweb.zel.kfa-juelich.de/projects/gigalink/)");
#ifdef MODULE_LICENSE
MODULE_LICENSE("GPL");
#endif
MODULE_SUPPORTED_DEVICE("sis1100/sis3100/sis5100; http://www.struck.de/pcivme.htm");

#define DRIVER_VERSION "2.02 alpha mki"

struct sis1100_softc *sis1100_devdata[sis1100_MAXCARDS];

struct file_operations sis1100_fops = {
	.owner   = THIS_MODULE,
	.llseek  = sis1100_llseek,
	.read    = sis1100_read,
	.write   = sis1100_write,
	.ioctl   = sis1100_ioctl,
	.mmap    = sis1100_mmap,
        .poll    = sis1100_poll,
	.open    = sis1100_open,
	.release = sis1100_release,
};

/*
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
*/

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)

#define dev2pci(d) container_of(d, struct pci_dev, dev)
#define dev2sc(d) pci_get_drvdata(dev2pci(d))

ssize_t
show_driver_version(struct device* d, char *buf)
{
    return snprintf(buf, PAGE_SIZE, DRIVER_VERSION "\n");
}
ssize_t
show_physaddr_plx(struct device* d, char *buf)
{
    struct sis1100_softc *sc=dev2sc(d);
    return snprintf(buf, PAGE_SIZE, "0x%08lx\n", sc->plx_addr);
}
ssize_t
show_physaddr_reg(struct device* d, char *buf)
{
    struct sis1100_softc *sc=dev2sc(d);
    return snprintf(buf, PAGE_SIZE, "0x%08lx\n", sc->reg_addr);
}
ssize_t
show_physaddr_rem(struct device* d, char *buf)
{
    struct sis1100_softc *sc=dev2sc(d);
    return snprintf(buf, PAGE_SIZE, "0x%08lx\n", sc->rem_addr);
}
ssize_t
show_remote_size(struct device* d, char *buf)
{
    struct sis1100_softc *sc=dev2sc(d);
    return snprintf(buf, PAGE_SIZE, "0x%x\n", sc->rem_size);
}
ssize_t
show_local_version(struct device* d, char *buf)
{
    struct sis1100_softc *sc=dev2sc(d);
    return snprintf(buf, PAGE_SIZE, "%d %d %d %d\n",
        sc->local_ident&0xff,
        (sc->local_ident>>8)&0xff,
        (sc->local_ident>>16)&0xff,
        (sc->local_ident>>24)&0xff);
}
ssize_t
show_remote_version(struct device* d, char *buf)
{
    struct sis1100_softc *sc=dev2sc(d);
    if (sc->remote_hw==sis1100_hw_invalid) {
        return snprintf(buf, PAGE_SIZE, "0 0 0 0\n");
    } else {
        return snprintf(buf, PAGE_SIZE, "%d %d %d %d\n",
            sc->remote_ident&0xff,
            (sc->remote_ident>>8)&0xff,
            (sc->remote_ident>>16)&0xff,
            (sc->remote_ident>>24)&0xff);
    }
}

ssize_t
dummy_store(struct device* d, const char *buf, size_t count)
{
    return 0;
}

DEVICE_ATTR(driver_version, 0444, show_driver_version, dummy_store);
DEVICE_ATTR(physaddr_plx, 0444, show_physaddr_plx, dummy_store);
DEVICE_ATTR(physaddr_reg, 0444, show_physaddr_reg, dummy_store);
DEVICE_ATTR(physaddr_rem, 0444, show_physaddr_rem, dummy_store);
DEVICE_ATTR(remote_size, 0444, show_remote_size, dummy_store);
DEVICE_ATTR(local_version, 0444, show_local_version, dummy_store);
DEVICE_ATTR(remote_version, 0444, show_remote_version, dummy_store);

#endif /* KERNEL_VERSION >= 2.6.0 */

void __init sis1100_print_info(void)
{
    printk(KERN_INFO "SIS1100 driver V" DRIVER_VERSION
            " (c) 08.Jan.2004 FZ Juelich\n");
}

int __init
sis1100_linux_init(struct pci_dev *dev)
{
	struct sis1100_softc *sc;
	int i, idx, res;

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
	printk(KERN_INFO "sis1100: found %s at %s\n", dev->name, dev->slot_name);
#else
	printk(KERN_INFO "sis1100: found sis1100 at %s\n", dev->slot_name);
#endif
/*
        printk(KERN_INFO "vendor=0x%04x\n", dev->vendor);
        printk(KERN_INFO "device=0x%04x\n", dev->device);
        printk(KERN_INFO "sub_vendor=0x%04x\n", dev->subsystem_vendor);
        printk(KERN_INFO "sub_device=0x%04x\n", dev->subsystem_device);
        printk(KERN_INFO "class=%d\n", dev->class);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
        printk(KERN_INFO "name=>%s<\n", dev->name);
#else
        printk(KERN_INFO "name=>%s<\n", dev->dev.name);
#endif
        printk(KERN_INFO "slot_name=>%s<\n", dev->slot_name);
*/
	for (idx = 0; idx < sis1100_MAXCARDS; idx++) {
		if (!sis1100_devdata[idx]) break;
	}
	if (idx == sis1100_MAXCARDS) return -ENOMEM;

	sc = kmalloc(sizeof(struct sis1100_softc), GFP_KERNEL);
	if (!sc) return -ENOMEM;
	sis1100_devdata[idx] = sc;
	sc->unit = idx;

        for (i=0; i<=sis1100_MINORUTMASK; i++) sc->fdatalist[i]=0;

	init_waitqueue_head(&sc->handler_wait);
	init_waitqueue_head(&sc->local_wait);
	init_waitqueue_head(&sc->remoteirq_wait);
	init_timer(&sc->link_up_timer);
        sc->link_up_timer.function=sis1100_link_up_handler;
        sc->link_up_timer.data=(unsigned long)sc;

        init_MUTEX (&sc->sem_hw);
        init_MUTEX (&sc->sem_fdata_list);
        spin_lock_init(&sc->lock_intcsr);
        spin_lock_init(&sc->handlercommand.lock);
        spin_lock_init(&sc->lock_doorbell);
        spin_lock_init(&sc->lock_lemo_status);
        sc->handlercommand.command=0;
        INIT_LIST_HEAD(&sc->fdata_list_head);
        init_completion(&sc->handler_completion);

        sc->handler_pid=kernel_thread(sis1100_irq_thread, sc, 0);
        if (sc->handler_pid<0) {
            printk(KERN_ERR "create sis1100_irq_handler: %d\n", sc->handler_pid);
            res=sc->handler_pid;
            goto fehler_kmalloc_sc;
        }

        sc->plxmembase=0;
        sc->reg_base=0;
        sc->rem_base=0;

	sc->plx_addr = pci_resource_start(dev, 0);
	sc->plxmemlen = pci_resource_len(dev, 0);
	sc->plxmembase = ioremap_nocache(sc->plx_addr, sc->plxmemlen);
        printk(KERN_INFO "sis1100: plx_addr=0x%08lx\n", sc->plx_addr);
        printk(KERN_INFO "mapped at %p (size=0x%x)\n", sc->plxmembase, sc->plxmemlen);
	if (!sc->plxmembase) {
            res=-ENOMEM;
            printk(KERN_ERR "sis1100: can't map plx space\n");
	    goto fehler_ioremap;
        }
	sc->reg_addr = pci_resource_start(dev, 2);
	sc->reg_size = pci_resource_len(dev, 2);
	sc->reg_base = ioremap_nocache(sc->reg_addr, sc->reg_size);
        printk(KERN_INFO "sis1100: reg_addr=0x%08lx\n", sc->reg_addr);
        printk(KERN_INFO "mapped at %p (size=0x%x)\n", sc->reg_base, sc->reg_size);
	if (!sc->reg_base) {
	    res=-ENOMEM;
            printk(KERN_ERR "sis1100: can't map register space\n");
	    goto fehler_ioremap;
        }
	sc->rem_addr = pci_resource_start(dev, 3);
	sc->rem_size = pci_resource_len(dev, 3);
        printk(KERN_INFO "sis1100: rem_addr=0x%08lx\n", sc->rem_addr);
        printk(KERN_INFO "sis1100: rem_size=0x%08lx\n", (unsigned long)sc->rem_size);
        do {
	    sc->rem_base = ioremap_nocache(sc->rem_addr, sc->rem_size);
            if (!sc->rem_base) sc->rem_size>>=1;
        } while (!sc->rem_base && sc->rem_size);
	if (sc->rem_base) {
            printk(KERN_INFO "sis1100: rem_addr=0x%08lx\n", sc->rem_addr);
            printk(KERN_INFO "mapped at %p (size=0x%x)\n", sc->rem_base, sc->rem_size);
        } else {
            printk(KERN_WARNING "sis1100: can't map remote space (size=0x%x)\n", sc->rem_size);
	    printk(KERN_WARNING "sis1100: mmap not available\n");
            sc->rem_size=0;
        }
	res = pci_request_regions(dev, "sis1100");
	if (res)
		goto fehler_ioremap;

	res = request_irq(dev->irq, sis1100_intr, SA_SHIRQ, "sis1100", sc);
	if (res) {
            printk(KERN_WARNING "sis1100: error installing irq\n");
	    goto fehler_request_regions;
        }

	sc->pcidev = dev;
	pci_set_drvdata(dev, sc);

	pci_set_master(dev);

#if 0
        sc->no_dma=0;
        /*if (!pci_set_dma_mask(dev, 0xffffffffffffffff)) {
            printk(KERN_WARNING "sis1100: 64bit DMA available (but not used yet).\n");
            sc->dma_dac=1;
        } else*/ if (!pci_set_dma_mask(dev, 0xffffffff)) {
            sc->dma_dac=0;
        } else {
            printk(KERN_WARNING "sis1100: DMA not available.\n");
            sc->no_dma=1; /* not used yet */
        }
#endif
        if (pci_set_dma_mask(dev, 0xffffffff)) {
            printk(KERN_WARNING "sis1100: DMA not available.\n");
	    goto fehler_request_irq;
        }
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
        pci_set_consistent_dma_mask(dev, 0xffffffff);
#endif

#ifndef USE_SGL
        if ((res=alloc_kiovec(1, &sc->iobuf))<0) {
                sc->iobuf=0;
                goto fehler_request_irq;
        }
#endif
        sc->descbuf.size=SGL_SIZE*sizeof(struct plx9054_dmadesc);
        sc->descbuf.cpu_addr=pci_alloc_consistent(sc->pcidev,
    	        sc->descbuf.size, &sc->descbuf.dma_handle);
        if (!sc->descbuf.cpu_addr) {
                printk(KERN_ERR "sis1100: pci_alloc_consistent failed\n");
                res=-ENOMEM;
                goto fehler_alloc_kiovec;
        }
        /*mem_map_reserve(virt_to_page(sc->descbuf.cpu_addr));*/
        printk(KERN_INFO "sis1100: descbuf.dma_handle=0x%08llx\n",
                (unsigned long long)sc->descbuf.dma_handle);
        /*
        printk(KERN_INFO "sis1100: descbuf.cpu_addr=0x%08llx\n",
                (unsigned long long)sc->descbuf.cpu_addr);
        */
    	res=-sis1100_init(sc);
	if (res) {
	    goto fehler_alloc_descbuf;
	}

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
/* create files for sysfs */
        device_create_file(&dev->dev, &dev_attr_driver_version);
        device_create_file(&dev->dev, &dev_attr_physaddr_plx);
        device_create_file(&dev->dev, &dev_attr_physaddr_reg);
        device_create_file(&dev->dev, &dev_attr_physaddr_rem);
        device_create_file(&dev->dev, &dev_attr_remote_size);
        device_create_file(&dev->dev, &dev_attr_local_version);
        device_create_file(&dev->dev, &dev_attr_remote_version);
#endif

    	return 0;

/*fehler_sis1100_init:*/
        sis1100_done(sc);

fehler_alloc_descbuf:
        /*mem_map_unreserve(virt_to_page(sc->descbuf.cpu_addr));*/
        pci_free_consistent(sc->pcidev, sc->descbuf.size,
                sc->descbuf.cpu_addr, sc->descbuf.dma_handle);

fehler_alloc_kiovec:
#ifndef USE_SGL
        free_kiovec(1, &sc->iobuf);
        /*mem_map_unreserve(virt_to_page(sc->descbuf.cpu_addr));*/
#endif

fehler_request_irq:
        free_irq(dev->irq, sc);

fehler_request_regions:
	pci_release_regions(dev);

fehler_ioremap:
	if (sc->plxmembase) iounmap((void *)sc->plxmembase);
	if (sc->reg_base) iounmap((void *)sc->reg_base);
	if (sc->rem_base) iounmap((void *)sc->rem_base);

/*fehler_irq_thread:*/
	{
            unsigned long flags;
            spin_lock_irqsave(&sc->handlercommand.lock, flags);
            sc->handlercommand.command=handlercomm_die;
            spin_unlock_irqrestore(&sc->handlercommand.lock, flags);
            wake_up(&sc->handler_wait);
            wait_for_completion (&sc->handler_completion);
	}

fehler_kmalloc_sc:
	sis1100_devdata[sc->unit] = 0;
	kfree(sc);
	pci_set_drvdata(dev, NULL);
        if (res>=0) {
            printk(KERN_ERR "sis1100_linux_init: res=%d\n", res);
            res=-EINVAL;
        }
    	return res;
}

void __exit
sis1100_linux_done(struct pci_dev *dev)
{
	struct sis1100_softc *sc;

	sc = pci_get_drvdata(dev);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
/* delete files from sysfs */
        device_remove_file(&dev->dev, &dev_attr_driver_version);
        device_remove_file(&dev->dev, &dev_attr_physaddr_plx);
        device_remove_file(&dev->dev, &dev_attr_physaddr_reg);
        device_remove_file(&dev->dev, &dev_attr_physaddr_rem);
        device_remove_file(&dev->dev, &dev_attr_remote_size);
        device_remove_file(&dev->dev, &dev_attr_local_version);
        device_remove_file(&dev->dev, &dev_attr_remote_version);
#endif

#ifndef USE_SGL
        free_kiovec(1, &sc->iobuf);
        /*mem_map_unreserve(virt_to_page(sc->descbuf.cpu_addr));*/
#endif
        pci_free_consistent(sc->pcidev, sc->descbuf.size,
                sc->descbuf.cpu_addr, sc->descbuf.dma_handle);

	sis1100_done(sc);
	free_irq(dev->irq, sc);
	del_timer_sync(&sc->link_up_timer);
	if (sc->handler_pid>=0) {
            unsigned long flags;
            spin_lock_irqsave(&sc->handlercommand.lock, flags);
            sc->handlercommand.command=handlercomm_die;
            spin_unlock_irqrestore(&sc->handlercommand.lock, flags);
            wake_up(&sc->handler_wait);
            wait_for_completion (&sc->handler_completion);
	}
	iounmap((void *)sc->plxmembase);
	iounmap((void *)sc->reg_base);
	iounmap((void *)sc->rem_base);
	sis1100_devdata[sc->unit] = 0;

	kfree(sc);
	pci_release_regions(dev);
	pci_set_drvdata(dev, NULL);
}

int __init
sis1100_linux_drvinit(void)
{
    sis1100_major=register_chrdev(0, "sis1100", &sis1100_fops);
    if (sis1100_major<0) {
        printk(KERN_WARNING "sis1100: unable to register device\n");
        return -EBUSY;
    }
    return 0;
}

void __exit
sis1100_linux_drvdone(void)
{
    unregister_chrdev(sis1100_major, "sis1100");
}
