/* $ZEL: sis1100_ioctl.c,v 1.16 2002/05/29 15:54:23 wuestner Exp $ */

#include "Copyright"

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/pci.h>
#include <asm/uaccess.h>
#include <errno.h>
#include <linux/delay.h>

#include <dev/pci/plxbaseio.h>
#include <dev/pci/sis1100var.h>

int
sis1100_ioctl(struct inode *inode, struct file *file,
	      unsigned int cmd, unsigned long arg)
{
    struct SIS1100_softc* sc=SIS1100SC(file);
    struct SIS1100_fdata* fd=SIS1100FD(file);
    int handled;

#define COPYIN(data_type) \
    do { \
        if (!(cmd&IOC_IN)) return -ENOTTY; \
        if (copy_from_user(&data, (void *)arg, sizeof(data_type))) \
	    return -EFAULT; \
    } while (0)

#define COPYOUT(data_type) \
    do { \
        if (!(cmd&IOC_OUT)) return -ENOTTY; \
        if (copy_to_user((void *)arg, &data, sizeof(data_type))) \
	    return -EFAULT; \
    } while (0)

/* for tests only */
#if 0
    handled=1;
    switch (cmd) {
	case PLXREADCFG: {
	    struct plxreg data;
	    COPYIN(struct plxreg);
	    pci_read_config_dword(sc->pcidev, data.offset, &data.val);
	    COPYOUT(struct plxreg);
	    } break;
	case PLXWRITECFG: {
	    struct plxreg data;
	    COPYIN(struct plxreg);
	    pci_write_config_dword(sc->pcidev, data.offset, data.val);
	    } break;
	case PLXREADREG: {
	    struct plxreg data;
	    COPYIN(struct plxreg);
	    data.val = _plxreadreg(sc, data.offset);
	    COPYOUT(struct plxreg);
	    } break;
	case PLXWRITEREG: {
	    struct plxreg data;
	    COPYIN(struct plxreg);
	    _plxwritereg(sc, data.offset, data.val);
	    } break;
	case PLXREADLOCAL0: {
	    struct plxreg data;
	    COPYIN(struct plxreg);
	    data.val = plxreadlocal0(sc, data.offset);
	    COPYOUT(struct plxreg);
	    } break;
	case PLXWRITELOCAL0: {
	    struct plxreg data;
	    COPYIN(struct plxreg);
	    plxwritelocal0(sc, data.offset, data.val);
	    } break;
	case PLXREADLOCAL1: {
	    struct plxreg data;
	    COPYIN(struct plxreg);
	    data.val = plxreadlocal1(sc, data.offset);
	    COPYOUT(struct plxreg);
	    } break;
	case PLXWRITELOCAL1: {
	    struct plxreg data;
	    COPYIN(struct plxreg);
	    plxwritelocal1(sc, data.offset, data.val);
	    } break;
	default:
            handled=0;
    }
    if (handled) return 0;
#endif
/* all devices */
    handled=1;
    switch (cmd) {
	case SIS1100_CLEAR_USE_COUNT: {
            while (MOD_IN_USE) MOD_DEC_USE_COUNT;
            MOD_INC_USE_COUNT;
	    } break;
	case SIS1100_CONTROL_READ: {
	    struct sis1100_ctrl_reg data;
	    COPYIN(struct sis1100_ctrl_reg);
            down(&sc->sem_hw);
	    data.val = plxreadlocal0(sc, data.offset&0x7ff);
            up(&sc->sem_hw);
            data.error=0;
	    COPYOUT(struct sis1100_ctrl_reg);
	    } break;
	case SIS1100_CONTROL_WRITE: {
	    struct sis1100_ctrl_reg data;
	    COPYIN(struct sis1100_ctrl_reg);
            down(&sc->sem_hw);
	    plxwritelocal0(sc, data.offset&0x7ff, data.val);
            up(&sc->sem_hw);
            data.error=0;
	    COPYOUT(struct sis1100_ctrl_reg);
	    } break;
	case SIS3100_CONTROL_READ: {
	    struct sis1100_ctrl_reg data;
            if (!sc->remote_ok) return -ENXIO;
	    COPYIN(struct sis1100_ctrl_reg);
            down(&sc->sem_hw);
	    data.val = plxreadlocal0(sc, (data.offset&0x7ff)+0x800);
            rmb();
            data.error=sis1100readreg(sc, prot_error);
            up(&sc->sem_hw);
	    COPYOUT(struct sis1100_ctrl_reg);
	    } break;
	case SIS3100_CONTROL_WRITE: {
	    struct sis1100_ctrl_reg data;
            if (!sc->remote_ok) return -ENXIO;
	    COPYIN(struct sis1100_ctrl_reg);
            down(&sc->sem_hw);
	    plxwritelocal0(sc, (data.offset&0x7ff)+0x800, data.val);
            rmb();
            data.error=sis1100readreg(sc, prot_error);
            up(&sc->sem_hw);
	    COPYOUT(struct sis1100_ctrl_reg);
	    } break;
	case SIS1100_IDENT: {
	    struct sis1100_ident data;

	    data.local.hw_type=sc->local_ident&0xff;
	    data.local.hw_version=(sc->local_ident>>8)&0xff;
	    data.local.fw_type=(sc->local_ident>>16)&0xff;
	    data.local.fw_version=(sc->local_ident>>24)&0xff;

	    data.remote.hw_type=sc->remote_ident&0xff;
	    data.remote.hw_version=(sc->remote_ident>>8)&0xff;
	    data.remote.fw_type=(sc->remote_ident>>16)&0xff;
	    data.remote.fw_version=(sc->remote_ident>>24)&0xff;

            data.remote_ok=sc->remote_ok;
            data.remote_online=(sis1100readreg(sc, sr)&sr_synch)==sr_synch;

	    COPYOUT(struct sis1100_ident);
	    } break;
	case SIS3100_RESET: {
            down(&sc->sem_hw);
            sis1100writereg(sc, cr, cr_rem_reset);
            sis3100writereg(sc, vme_master_sc, 8, 1);
            up(&sc->sem_hw);
            mdelay(500);
            sis1100_init_remote(sc);
	    } break;
	case SIS1100_DEVTYPE: {
            int data;
            data=fd->subdev;
            COPYOUT(int);
	    } break;
#if 0
        case SIS1100_BIGENDIAN: {
            int data, tmp;
            COPYIN(int);
            tmp=fd->dma_big_endian;
            if (data>=0) {
                fd->dma_big_endian=!!data;
                if (fd->dma_big_endian)
                    sis1100writereg(sc, cr, 8); /* big endian */
                else
                    sis1100writereg(sc, cr, 8<<16); /* little endian */
            }
            tmp=sis1100readreg(sc, cr);
            printk(KERN_INFO "CONTROL=0x%04x\n", tmp);
            data=!!(tmp&8);
            COPYOUT(int);
            } break;
#endif
        case SIS1100_MINDMALEN: {
            int data[2], tmp[2];
            COPYIN(int[2]);
            tmp[0]=fd->mindmalen_r;
            tmp[1]=fd->mindmalen_w;
            printk(KERN_INFO "SIS1100_MINDMALEN: r=%d w=%d\n",
                data[0], data[1]);
            if (data[0]>=0) fd->mindmalen_r=data[0];
            if (data[1]>=0) fd->mindmalen_w=data[1];
            data[0]=tmp[0];
            data[1]=tmp[1];
            COPYOUT(int[2]);
            } break;
  	case SIS1100_SETVMESPACE: {
            struct vmespace data;
            COPYIN(struct vmespace);
            if ((data.datasize!=1) && (data.datasize!=2) && (data.datasize!=4))
                return -EINVAL;
            fd->vmespace_am=data.am;
            fd->vmespace_datasize=data.datasize;
            fd->big_endian=data.swap;
            if (data.mindmalen>=0) {
                fd->mindmalen_r=data.mindmalen;
                fd->mindmalen_w=data.mindmalen;
            }
  	    } break;
	case SIS1100_FRONT_IO: {
            u_int32_t data;
            COPYIN(u_int32_t);
            sis1100_front_io(sc, &data, 0);
            COPYOUT(u_int32_t);
            } break;
	case SIS1100_FRONT_PULSE: {
            u_int32_t data;
            COPYIN(u_int32_t);
            sis1100_front_io(sc, &data, 0);
            } break;
	case SIS1100_FRONT_LATCH: {
            u_int32_t data;
            COPYIN(u_int32_t);
            sis1100_front_io(sc, &data, 0);
            COPYOUT(u_int32_t);
            } break;
	case SIS1100_LAST_ERROR: {
	    u_int32_t data;
            data=fd->last_prot_err;
	    COPYOUT(u_int32_t);
	    } break;
	case SIS1100_DUMP: {
            dump_glink_status(sc, "DUMP requested by USER", 0);
	    } break;
	default:
            handled=0;
    }
    if (handled) return 0;

/* VME only */
    if (fd->subdev==sis1100_subdev_vme) {
        handled=1;
        switch (cmd) {
    	    case SIS1100_MAPINFO: {
	        struct sis1100_mapinfo data;
	        COPYIN(struct sis1100_mapinfo);
	        switch (data.space) {
                    case 0:
		        data.offset=0;
		        data.size=sc->plxmemlen;
		        return -EINVAL;
		        break;
		    case 1:
		        data.offset=PAGE_ALIGN(sc->plxmemlen);
		        data.size=sc->plxlocallen0;
		        return -EINVAL;
		        break;
		    case 2:
		        /*
                        data.offset=PAGE_ALIGN(sc->plxmemlen)+
			        PAGE_ALIGN(sc->plxlocallen0);
                        */
                        data.offset=0;
		        data.size=sc->plxlocallen1;
		        break;
		    default:
		        return -EINVAL;
	        }
	        COPYOUT(struct sis1100_mapinfo);
	        } break;
	    case SIS1100_PIPE: {
	        struct sis1100_pipe data;
	        int res;
                if (!sc->remote_ok) return -ENXIO;
	        COPYIN(struct sis1100_pipe);

    	        res=sis1100_read_pipe(sc, &data);

	        if (res) return res;
	        COPYOUT(struct sis1100_pipe);
	        } break;
	    case SIS3100_VME_PROBE: {
	        int data, dummy;
                if (!sc->remote_ok) return -ENXIO;
	        COPYIN(int);
	        if (sis1100_tmp_read(sc, data, fd->vmespace_am,
		        fd->vmespace_datasize, 1/*space*/, &dummy))
		    return -EIO;
	        } break;
	    case SIS3100_VME_READ: {
	        struct sis1100_vme_req data;
                if (!sc->remote_ok) return -ENXIO;
	        COPYIN(struct sis1100_vme_req);
                data.data=0;
	        data.error=sis1100_tmp_read(sc, data.addr, data.am, data.size,
		        1/*space*/, &data.data);
	        COPYOUT(struct sis1100_vme_req);
	        } break;
	    case SIS3100_VME_WRITE: {
	        struct sis1100_vme_req data;
                if (!sc->remote_ok) return -ENXIO;
	        COPYIN(struct sis1100_vme_req);
	        data.error=sis1100_tmp_write(sc, data.addr, data.am, data.size,
		        1/*space*/, data.data);
	        COPYOUT(struct sis1100_vme_req);
	        } break;
	    case SIS3100_VME_BLOCK_READ: {
	        struct sis1100_vme_block_req data;
                ssize_t res;
                if (!sc->remote_ok) return -ENXIO;
	        COPYIN(struct sis1100_vme_block_req);
	        res=sis1100_read_dma(fd, data.addr, data.am,
	            data.size, 1/*VME-Space*/, data.fifo, data.size*data.num,
                    (u_int8_t*)data.data, &data.error);
                if (res>0)
                    data.num=res/data.size;
                else
                    data.num=0;
	        if (res<0) return res;
	        COPYOUT(struct sis1100_vme_block_req);
	        } break;
	    case SIS3100_VME_SUPER_BLOCK_READ: {
	        struct sis1100_vme_super_block_req data;
	        struct sis1100_vme_block_req reqs[10];
                ssize_t res;
                int i;
                if (!sc->remote_ok) return -ENXIO;
	        COPYIN(struct sis1100_vme_super_block_req);
                if (copy_from_user(reqs, (void *)data.reqs,
                        data.n*sizeof(struct sis1100_vme_block_req)))
	            return -EFAULT;
                for (i=0; i<data.n; i++) {
	            res=sis1100_read_dma(fd, reqs[i].addr, reqs[i].am,
	                reqs[i].size, 1/*VME-Space*/, reqs[i].fifo,
                        reqs[i].size*reqs[i].num,
                        (u_int8_t*)reqs[i].data, &reqs[i].error);
                    if (res>0)
                        reqs[i].num=res/reqs[i].size;
                    else
                        reqs[i].num=0;
	            if (res<0) return res;
                }
	        if (copy_to_user((void *)data.reqs, reqs,
                data.n*sizeof(struct sis1100_vme_block_req)))
	            return -EFAULT;

	        } break;
	    case SIS3100_VME_BLOCK_WRITE: {
	        struct sis1100_vme_block_req data;
                ssize_t res;
                if (!sc->remote_ok) return -ENXIO;
	        COPYIN(struct sis1100_vme_block_req);
	        res=sis1100_write_dma(fd, data.addr, data.am,
	            data.size, 1/*VME-Space*/, data.fifo, data.size*data.num,
                    (u_int8_t*)data.data, &data.error);
                if (res>0)
                    data.num=res/data.size;
                else
                    data.num=0;
	        if (res<0) return res;
	        COPYOUT(struct sis1100_vme_block_req);
	        } break;
            case SIS1100_FIFOMODE: {
                int data, tmp;
                COPYIN(int);
                tmp=fd->fifo_mode;
                if (data>=0) fd->fifo_mode=!!data;
                data=tmp;
                COPYOUT(int);
                } break;
	    case SIS1100_IRQ_CTL: {
	        struct sis1100_irq_ctl data;
                int res;
                COPYIN(struct sis1100_irq_ctl);
                res=sis1100_irq_ctl(fd, &data);
                if (res) return res;
	        } break;
	    case SIS1100_IRQ_GET: {
	        struct sis1100_irq_get data;
                int res;
                COPYIN(struct sis1100_irq_get);
                res=sis1100_irq_get(fd, &data);
                if (res) return res;
                COPYOUT(struct sis1100_irq_get);
	        } break;
	    case SIS1100_IRQ_ACK: {
	        struct sis1100_irq_ack data;
                int res;
                COPYIN(struct sis1100_irq_ack);
                res=sis1100_irq_ack(fd, &data);
                if (res) return res;
	        } break;
	    case SIS1100_IRQ_WAIT: {
	        struct sis1100_irq_get data;
                int res;
                COPYIN(struct sis1100_irq_get);
                res=sis1100_irq_wait(fd, &data);
                if (res) return res;
                COPYOUT(struct sis1100_irq_get);
	        } break;
	    default:
                handled=0;
        }
        if (handled) return 0;
    }
/* ram only */
    if (fd->subdev==sis1100_subdev_ram) {
        handled=1;
        switch (cmd) {
	    default:
                handled=0;
        }
        if (handled) return 0;
    }
/* sharc only */
    if (fd->subdev==sis1100_subdev_sharc) {
        handled=1;
        switch (cmd) {
	    default:
                handled=0;
        }
        if (handled) return 0;
    }
    return -ENOTTY;
}
