#include "ldevusbu.h"
#include "lcomp_driver_version.h"

MODULE_DESCRIPTION("Driver for L Card USB devices");
MODULE_AUTHOR("L Card");
MODULE_LICENSE("GPL");
MODULE_VERSION(LCOMP_DRIVER_VERSION);

static long ldevusb_stop(ldevusb *dev);

static void ldev_io_urb_complete(struct urb *urb) {
    unsigned long flags;
    t_ldev_io *io = urb->context;
    int status = urb->status;

    spin_lock_irqsave(&io->lock, flags);

    /* In 2.5 we require hcds' endpoint queues not to progress after fault
         * reports, until the completion callback (this!) returns.  That lets
         * device driver code (like this routine) unlink queued urbs first,
         * if it needs to, since the HC won't work on them at all.  So it's
         * not possible for page N+1 to overwrite page N, and so on.
         *
         * That's only for "hard" faults; "soft" faults (unlinks) sometimes
         * complete before the HCD can get requests away from hardware,
         * though never during cleanup after a hard fault.
         */
    if (io->status
            && (io->status != -ECONNRESET
                || status != -ECONNRESET)
            && urb->actual_length) {
        dev_err(io->dev->bus->controller,
                "dev %s ep%d%s scatterlist error %d/%d\n",
                io->dev->devpath,
                usb_endpoint_num(&urb->ep->desc),
                usb_urb_dir_in(urb) ? "in" : "out",
                status, io->status);
        /* BUG (); */
    }

    if (io->status == 0 && status && status != -ECONNRESET) {
        int i, found, retval;

        io->status = status;

        /* the previous urbs, and this one, completed already.
             * unlink pending urbs so they won't rx/tx bad data.
             * careful: unlink can sometimes be synchronous...
             */
        spin_unlock_irqrestore(&io->lock, flags);
        for (i = 0, found = 0; i < io->entries; i++) {
            if (!io->urbs[i])
                continue;
            if (found) {
                usb_block_urb(io->urbs[i]);
                retval = usb_unlink_urb(io->urbs[i]);
                if (retval != -EINPROGRESS &&
                        retval != -ENODEV &&
                        retval != -EBUSY &&
                        retval != -EIDRM)
                    dev_err(&io->dev->dev,
                            "%s, unlink --> %d\n",
                            __func__, retval);
            } else if (urb == io->urbs[i])
                found = 1;
        }
        spin_lock_irqsave(&io->lock, flags);
    }

    /* on the last completion, signal usb_sg_wait() */
    io->bytes += urb->actual_length;
    io->count--;
    if (!io->count)
        complete(&io->complete);

    spin_unlock_irqrestore(&io->lock, flags);
}


static void ldev_io_clean(t_ldev_io *io) {
    if (io->urbs) {
        while (io->entries--)
            usb_free_urb(io->urbs[io->entries]);
        kfree(io->urbs);
        io->urbs = NULL;
    }
    io->dev = NULL;
    kfree(io->mem_sg);
    io->mem_sg = NULL;
}


static int ldev_io_alloc(t_ldev_io *io, struct usb_device *dev, unsigned pipe, int len) {
    int status = 0;
    int pages_cnt = len * sizeof(u16)/ PAGE_SIZE;
    gfp_t mem_flags = GFP_KERNEL;

    memset(io, 0, sizeof(t_ldev_io));

    io->dev = dev;
    io->pipe = pipe;
    io->total_len = len;
    io->total_bytes = len * sizeof(u16);

    io->mem_sg = kmalloc_array(pages_cnt, sizeof(*io->mem_sg), mem_flags);

    if (io->mem_sg == NULL) {
        status = -ENOMEM;
    } else {
        if (io->dev->bus->sg_tablesize > 0) {
            io->use_sg = true;
            io->entries = 1;
        } else {
            io->use_sg = false;
            io->entries = pages_cnt;
        }

        /* initialize all the urbs we'll use */
        io->urbs = kmalloc_array(io->entries, sizeof(*io->urbs), mem_flags);
        if (!io->urbs) {
            status = -ENOMEM;
        } else {
            int i;
            int urb_flags = URB_NO_INTERRUPT;
            if (usb_pipein(io->pipe))
                urb_flags |= URB_SHORT_NOT_OK;

            for (i = 0; i < io->entries; i++) {
                struct urb *urb;
                unsigned len;

                urb = usb_alloc_urb(0, mem_flags);
                if (!urb) {
                    io->entries = i;
                    status = -ENOMEM;
                    break;
                }

                io->urbs[i] = urb;

                urb->dev = NULL;
                urb->pipe = io->pipe;
                urb->transfer_flags = urb_flags;
                urb->complete = ldev_io_urb_complete;
                urb->context = io;

                if (io->use_sg) {
                    /* There is no single transfer buffer */
                    urb->transfer_buffer = NULL;
                    urb->num_sgs = pages_cnt;

                    len = io->total_bytes;
                } else {
                    urb->transfer_buffer = NULL;

                    len = PAGE_SIZE;
                }
                urb->transfer_buffer_length = len;
            }
            io->urbs[--i]->transfer_flags &= ~URB_NO_INTERRUPT;
        }
    }

    if (status != 0)
        ldev_io_clean(io);

    return status;
}

static void ldev_io_cancel(t_ldev_io *io) {
    unsigned long flags;
    int i, retval;

    spin_lock_irqsave(&io->lock, flags);
    if (io->status || io->count == 0) {
        spin_unlock_irqrestore(&io->lock, flags);
        return;
    }
    /* shut everything down */
    io->status = -ECONNRESET;
    io->count++;		/* Keep the request alive until we're done */
    spin_unlock_irqrestore(&io->lock, flags);

    for (i = io->entries - 1; i >= 0; --i) {
        usb_block_urb(io->urbs[i]);

        retval = usb_unlink_urb(io->urbs[i]);
        if (retval != -EINPROGRESS
            && retval != -ENODEV
            && retval != -EBUSY
            && retval != -EIDRM)
            dev_warn(&io->dev->dev, "%s, unlink --> %d\n",
                 __func__, retval);
    }

    spin_lock_irqsave(&io->lock, flags);
    io->count--;
    if (!io->count)
        complete(&io->complete);
    spin_unlock_irqrestore(&io->lock, flags);
}


static int ldev_io_submit(t_ldev_io *io, hb *hbuf, int pos) {
    int status = 0;
    int page = pos * sizeof(u16)/PAGE_SIZE + 1;
    int pages_cnt = io->total_bytes/PAGE_SIZE;
    int page_idx;
    struct scatterlist *sg;
    int i;

    sg_init_table(io->mem_sg, pages_cnt);

    page_idx = page;
    for_each_sg(io->mem_sg, sg, pages_cnt, i) {
        sg_set_buf(sg, hbuf->addr[page_idx], PAGE_SIZE);
        page_idx++;
    }

    for_each_sg(io->mem_sg, sg, io->entries, i) {
        io->urbs[i]->sg  = sg;
        if (io->use_sg) {
            io->urbs[i]->transfer_buffer = NULL;
            io->urbs[i]->num_sgs = pages_cnt;
        } else {
            if (!PageHighMem(sg_page(sg)))
                io->urbs[i]->transfer_buffer = sg_virt(sg);
            else
                io->urbs[i]->transfer_buffer = NULL;
        }
    }
    /* transaction state */
    io->count = io->entries;
    io->status = 0;
    io->bytes = 0;
    init_completion(&io->complete);

    /* queue the urbs.  */
    spin_lock_irq(&io->lock);
    i = 0;
    while ((i < io->entries) && !io->status) {
        int retval;
        io->urbs[i]->dev = io->dev;
        spin_unlock_irq(&io->lock);
        retval = usb_submit_urb(io->urbs[i], GFP_NOIO);

        switch (retval) {
        /* maybe we retrying will recover */
        case -ENXIO:	/* hc didn't queue this one */
        case -EAGAIN:
        case -ENOMEM:
            retval = 0;
            yield();
            break;

            /* no error? continue immediately.
                 *
                 * NOTE: to work better with UHCI (4K I/O buffer may
                 * need 3K of TDs) it may be good to limit how many
                 * URBs are queued at once; N milliseconds?
                 */
        case 0:
            ++i;
            cpu_relax();
            break;

            /* fail any uncompleted urbs */
        default:
            io->urbs[i]->status = retval;
            /** @todo */
            ldev_io_cancel(io);
        }
        spin_lock_irq(&io->lock);
        if (retval && (io->status == 0 || io->status == -ECONNRESET))
            io->status = retval;
    }
    io->count -= io->entries - i;
    if (io->count == 0)
        complete(&io->complete);
    spin_unlock_irq(&io->lock);


    return status;
}


static int ldev_io_thread_func(void *ctx) {
    int status = 0;
    int transf_tx;
    ldevusb *ldev = (ldevusb *)(ctx);
    int hb_total_size = ldev->RealPages * PAGE_SIZE / sizeof(u16);
    bool stop_req = false;

    ldev->rx_ctx.start_io_pos =  ldev->rx_ctx.finish_io_pos = 0;

    for (transf_tx = 0; (transf_tx < LDEV_IO_TRANSF_CNT) && (status == 0); transf_tx++) {
        status = ldev_io_submit(&ldev->rx_ctx.transfs[transf_tx], ldev->Sync, ldev->rx_ctx.start_io_pos);
        if (status == 0) {
            ldev->rx_ctx.start_io_pos += ldev->rx_ctx.transfs[transf_tx].total_len;
            if (ldev->rx_ctx.start_io_pos == hb_total_size)
                ldev->rx_ctx.start_io_pos = 0;
        }
    }

    transf_tx = 0;
    while ((status == 0) && !stop_req) {
        t_ldev_io *transf = &ldev->rx_ctx.transfs[transf_tx];
        wait_for_completion(&transf->complete);
        status = transf->status;


        if (kthread_should_stop() || !test_bit(FLAG_WORKING,&ldev->dev_flags)) {
            stop_req = true;
            DbgPrint("thread stop requested");
        }


        if (status == 0) {
             ldev->rx_ctx.finish_io_pos += transf->total_len;
             *ldev->SyncCntr = ldev->rx_ctx.finish_io_pos;
             DbgPrint("set sync cntr = %d", ldev->rx_ctx.finish_io_pos);
             mb();
             if (ldev->rx_ctx.finish_io_pos == hb_total_size) {
                 ldev->rx_ctx.finish_io_pos = 0;
                 /** @todo autoinit stop */

                 /* mb();
                    *dev->SyncCntr = 0;
                    set_bit(FLAG_ADC_EVT,&dev->dev_flags);
                    wake_up_interruptible(&dev->adc_wq); */
             }

             if (!stop_req) {
                 status = ldev_io_submit(&ldev->rx_ctx.transfs[transf_tx], ldev->Sync, ldev->rx_ctx.start_io_pos);
                 if (status == 0) {
                     ldev->rx_ctx.start_io_pos += ldev->rx_ctx.transfs[transf_tx].total_len;
                     if (ldev->rx_ctx.start_io_pos == hb_total_size)
                         ldev->rx_ctx.start_io_pos = 0;
                 }

                 if (++transf_tx == LDEV_IO_TRANSF_CNT)
                     transf_tx = 0;
             }
        }
    }

    /* отменяем все оставшиеся запросы */
    for (transf_tx = 0; transf_tx < LDEV_IO_TRANSF_CNT; transf_tx++) {
        t_ldev_io *transf = &ldev->rx_ctx.transfs[transf_tx];
        ldev_io_cancel(transf);
    }

    /* ожидаем завершения не завершенных еще запросов */
    for (transf_tx = 0; transf_tx < LDEV_IO_TRANSF_CNT; transf_tx++) {
        unsigned long flags;
        int wip = 0;
        t_ldev_io *transf = &ldev->rx_ctx.transfs[transf_tx];
        spin_lock_irqsave(&transf->lock, flags);
        wip = transf->count > 0;
        spin_unlock_irqrestore(&transf->lock, flags);
        if (wip) {
            wait_for_completion(&transf->complete);
        }
    }

    return status;
}


//////////////////////// HUGE BUFFER FW ////////////////////////////////////
void hb_free(hb *buf) {
   int i;
   for(i=0; i < buf->nr_pages; i++)
       free_page((unsigned long)buf->addr[i]);
   kfree(buf->addr);
   kfree(buf);
}

hb *hb_malloc(int pages) {
   int i;
   hb *buf = (hb *)kmalloc(sizeof(hb),GFP_KERNEL);
   if(buf == NULL)
       return NULL;

   buf->nr_pages = pages;
   buf->addr = kmalloc(buf->nr_pages*sizeof(u8 *), GFP_KERNEL);
   if(!buf->addr) {
       kfree(buf);
       return NULL;
   }

   memset(buf->addr, 0, buf->nr_pages*sizeof(u8 *));
   for(i = 0; i < buf->nr_pages; i++) {
      buf->addr[i] = (u8 *)__get_free_page(GFP_KERNEL);
      if(buf->addr[i] == NULL) {
          buf->nr_pages=i;
          hb_free(buf);
          return NULL;
      }
   }
   return buf;
}

#define hb_getel(buf, pos, type) (((type *)(buf->addr[pos/(PAGE_SIZE/sizeof(type))]))[pos%(PAGE_SIZE/sizeof(type))])
#define hb_setel(buf, pos, type, data) {((type *)(buf->addr[pos/(PAGE_SIZE/sizeof(type))]))[pos%(PAGE_SIZE/sizeof(type))] = data;}
#define hb_addel(buf, pos, type, data) {((type *)(buf->addr[pos/(PAGE_SIZE/sizeof(type))]))[pos%(PAGE_SIZE/sizeof(type))] += data;}
#define hb_decel(buf, pos, type, data) {((type *)(buf->addr[pos/(PAGE_SIZE/sizeof(type))]))[pos%(PAGE_SIZE/sizeof(type))] -= data;}


int hb_memcpyto(u8 *src, hb *buf, int pos, int size)
{
   int page = pos/PAGE_SIZE;
   int off =  pos%PAGE_SIZE;
   int len = PAGE_SIZE-off;
   do
   {
      if(size<len) len=size;
      memcpy((void *)(buf->addr[page]+off), (void *)src, len);
      src+=len;
      page++;
      size-=len;
      off=0;
      len=PAGE_SIZE;
   } while(size);
   return 0;
}

int hb_memcpyfrom(u8 *dst, hb *buf, int pos, int size)
{
   int page = pos/PAGE_SIZE;
   int off =  pos%PAGE_SIZE;
   int len = PAGE_SIZE-off;
   do
   {
      if(size<len) len=size;
      memcpy((void *)dst, (void *)(buf->addr[page]+off), len);
      dst+=len;
      page++;
      size-=len;
      off=0;
      len=PAGE_SIZE;
   } while(size);
   return 0;
}

//////////////////////////////////////////////////////////////////



// forward decl
static char device_name[] = "ldevusb";
static struct file_operations fops;
static struct usb_driver ldriver;
static void ldevusb_delete(struct kref *kref);

static int ldriver_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
   ldevusb *dev = NULL;
   struct usb_host_interface *iface_desc;
   struct usb_endpoint_descriptor *endpoint;
   int i;

   int retval = -ENOMEM;
   int status = 0;

   /* allocate memory for our device state and initialize it */
   dev = kmalloc(sizeof(ldevusb), GFP_KERNEL);
   if (dev == NULL) { DbgPrint("Out of memory"); goto error; }
   memset(dev, 0x00, sizeof (*dev));
   kref_init(&dev->kref);

   dev->Slot = ldev_add();

   dev->udev = usb_get_dev(interface_to_usbdev(interface));
   dev->interface = interface;
   interface->minor=dev->Slot;

   /* set up the endpoint information */
   /* use only the first bulk-in and bulk-out endpoints */
   iface_desc = interface->cur_altsetting;
   for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i)
   {
      endpoint = &iface_desc->endpoint[i].desc;

      if (!dev->bulk_in_endpointAddr &&
          (endpoint->bEndpointAddress & USB_DIR_IN) &&
          ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
               == USB_ENDPOINT_XFER_BULK))
      {
         dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
      }

      if (!dev->bulk_out_endpointAddr &&
          !(endpoint->bEndpointAddress & USB_DIR_IN) &&
          ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
               == USB_ENDPOINT_XFER_BULK))
      {
         dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
      }
   }
   if(!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr))
   {
      DbgPrint("Could not find both bulk-in and bulk-out endpoints");
      goto error;
   }

   /* save our data pointer in this interface device */
   usb_set_intfdata(interface, dev);

// fill up SLOT_PAR for client
   memset(&dev->sl,0,sizeof(SLOT_PAR));
   switch(id->idProduct)
   {
      case 0x0440: {dev->Type = E440; dev->sl.DSPType = 2185;} break;
      case 0x2010: {dev->Type = E2010; dev->sl.DSPType = 0;} break;
      case 0x0140: {dev->Type = E140; dev->sl.DSPType = 0; } break;
      case 0x0154: {dev->Type = E154; dev->sl.DSPType = 0; }
   }

   switch(dev->Type)
   {
      case E440: break;
      case E140:
      {
      
         char *mn = (char *)kmalloc(256,GFP_KERNEL);
         int status = -1;
   
         status = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
                     V_GET_MODULE_NAME_E140, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
                     0, 0, mn, 255, HZ*10);
         
         DbgPrint("############# module name  %s %x \n",mn, status);
	kfree(mn);
         
      }       break;
      case E154:
       break;
      case E2010:
        status = ldev_e2010_init(dev);
      break;
   };

   dev->sl.BoardType = dev->Type;

   init_waitqueue_head(&dev->adc_wq);
   mutex_init(&dev->io_lock);
   dev->dev_flags=0;

   DbgPrint("ldevusb device now attached to ldev%d \n", dev->Slot);

   ldev_register(&fops, dev->Slot);

   return 0;

error:
   if(dev)  kref_put(&dev->kref, ldevusb_delete);
   return retval;
}

static void ldevusb_delete(struct kref *kref)
{
   ldevusb *dev = to_ldevusb_dev(kref);
   kfree(dev->devspec);
   DbgPrint("in delete \n");
   usb_put_dev(dev->udev);
   ldev_remove(dev->Slot);
   kfree(dev);
}


static void ldriver_disconnect(struct usb_interface *interface)
{
   ldevusb *dev;

   /* prevent skel_open() from racing skel_disconnect() */
/////!!!!   lock_kernel();

   dev = usb_get_intfdata(interface);
   usb_set_intfdata(interface, NULL);

/////!!!!   unlock_kernel();

   /* decrement our usage count */
   kref_put(&dev->kref, ldevusb_delete);
   DbgPrint("ldev%d now disconnected \n", dev->Slot);
}

static int ldevusb_open(struct inode *inode, struct file *filp)
{
   ldevusb *dev;
   struct usb_interface *interface;
   int subminor;
//   u32 Addr;
//   u32 LongAddr;

   subminor = iminor(inode);
   DbgPrint("subminor %d", subminor);
   interface = usb_find_interface(&ldriver, subminor);
   if(!interface)
   {
      DbgPrint ("%s - error, can't find device for minor %d",  __FUNCTION__, subminor);
      return -ENODEV;
   }

   dev = usb_get_intfdata(interface);
   if(!dev) { return -ENODEV; }

   mutex_lock(&dev->io_lock);
   {
      if(test_bit(FLAG_USED,&dev->dev_flags)) { mutex_unlock(&dev->io_lock); return -EBUSY; }

      set_bit(FLAG_USED,&dev->dev_flags);
      clear_bit(FLAG_WORKING,&dev->dev_flags);

      dev->Sync = NULL;
      dev->bios = NULL;


      kref_get(&dev->kref);
      filp->private_data = dev;
   }
   mutex_unlock(&dev->io_lock);
   return 0;
}

static int ldevusb_release(struct inode *inode, struct file *filp) {
   ldevusb *dev = (ldevusb *)filp->private_data;
   if(dev==NULL) return -ENODEV;

   mutex_lock(&dev->io_lock);
   {
       ldevusb_stop(dev);
/*
      if(dev->OutBulk)
      {
         for (i = 0; i < (dev->Pages*PAGE_SIZE); i+= PAGE_SIZE)
         {
            ClearPageReserved(vmalloc_to_page((void *)(((unsigned long)dev->OutBulk) + i)));
         }
         vfree(dev->OutBulk);
         dev->OutBulk=NULL;
      }
*/

      if(dev->Sync)
          hb_free(dev->Sync);

      if(dev->bios) {
          kfree(dev->bios);
          dev->bios=NULL;
      }

      clear_bit(FLAG_USED,&dev->dev_flags);
      kref_put(&dev->kref, ldevusb_delete);
   }
   mutex_unlock(&dev->io_lock);
   return 0;
}

static long ldevusb_stop(ldevusb *dev) {
    long status = 0;
    if(test_bit(FLAG_WORKING,&dev->dev_flags)) {
        int i;
        clear_bit(FLAG_WORKING,&dev->dev_flags);
        ldevusb_enable(dev, 0);
        for (i = 0; i < LDEV_IO_TRANSF_CNT; i++) {
            ldev_io_cancel(&dev->rx_ctx.transfs[i]);
        }
        if (dev->io_thread != NULL) {
            kthread_stop(dev->io_thread);
            put_task_struct(dev->io_thread);
            dev->io_thread = NULL;
        }
        for (i = 0; i < LDEV_IO_TRANSF_CNT; i++) {
            ldev_io_clean(&dev->rx_ctx.transfs[i]);
        }
    }
    return status;
}

static /*int*/long ldevusb_ioctl(/*struct inode *inode,*/ struct file *filp, unsigned int cmd, unsigned long arg) {
    ldevusb *dev = (ldevusb *)filp->private_data;
    int status = -EFAULT;
    PIOCTL_BUFFER ibuf;
    u16 Param, Param1;
    u16 Dir,Cmd,Addr,Index;
    u32 Param32;
    u16 AdcCoef[24];

   ibuf = kmalloc(sizeof(IOCTL_BUFFER),GFP_KERNEL);
   if(ibuf==NULL) return status;
   if(copy_from_user(ibuf, (void*)arg, sizeof(IOCTL_BUFFER))) {kfree(ibuf); return status;}

   if(cmd==DIOC_WAIT_COMPLETE)
   {
         DbgPrint("wait start \n");
         wait_event_interruptible(dev->adc_wq, test_bit(FLAG_ADC_EVT,&dev->dev_flags));
         clear_bit(FLAG_ADC_EVT,&dev->dev_flags);
         DbgPrint("wait complete \n");
         return 0;
   }


    mutex_lock(&dev->io_lock);

    switch(cmd) {
      case DIOC_GET_PARAMS:
      {
         DbgPrint("in DIOC_GET_PARAMS \n");
         memcpy(ibuf->outBuffer, &(dev->sl), sizeof(SLOT_PAR));
         status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
      } break;

      case DIOC_SET_DSP_TYPE:
      {
         DbgPrint("In DIOC_SET_DSP_TYPE \n");
         DbgPrint("Only 2185 or no DSP at all\n");
         status = 0;
      } break;

      case DIOC_SEND_COMMAND:
      {
         DbgPrint("in DIOC_SEND_COMMAND \n");
         // 0(dir 0out 1 in) 1 -cmd  2 -addr 3 len/index
         Dir = ((u16 *)ibuf->inBuffer)[0];
         Cmd = ((u16 *)ibuf->inBuffer)[1];
         Addr = ((u16 *)ibuf->inBuffer)[2];
         Index = ((u16 *)ibuf->inBuffer)[3];

         DbgPrint("%d %d %d %d", Dir, Cmd, Addr, Index);

         if(Dir)
         {
            status = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
                     (u8)Cmd, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
                     Addr, Index, ibuf->outBuffer, ibuf->outSize, HZ*10);
         }
         else
         {
            status = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
                     (u8)Cmd, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_OUT,
                     Addr, Index, ibuf->outBuffer, ibuf->outSize, HZ*10);
         }
         if(status<0) break;
         ibuf->outSize = status;
         if(copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER))) break;
         status=0;
      } break;

        case DIOC_SEND_BIOS: {
            status = 0;
            Param32 = *(u32 *)ibuf->inBuffer;
            if(dev->bios == NULL) {
                dev->bios = (u8 *) kmalloc(Param32,GFP_KERNEL);
                if(dev->bios == NULL) {
                    status = -ENOMEM;
                }
                dev->bios_pos = dev->bios;
            }

            if (dev->bios_pos != NULL) {
                memcpy(dev->bios_pos, ibuf->outBuffer, ibuf->outSize);
                dev->bios_pos += ibuf->outSize;
            }
        }
        break;
        case DIOC_LOAD_BIOS: {
            status = 0;
            Param32 = *(u32 *)ibuf->inBuffer;

            if(dev->bios || (Param32 == 0)) {
                if ((dev->Type == E2010B) || (dev->Type == E2010)) {
                    status = ldev_e2010_load_bios(dev, dev->bios, Param32);
                }
                kfree(dev->bios);
                dev->bios=NULL;
            }
        }
        break;

      case DIOC_READ_FLASH_WORD:
      {
         DbgPrint("In DIOC_READ_FLASH_WORD \n");
         Param = *(u16 *)ibuf->inBuffer;
         switch(dev->Type)
         {
            case E440: ReadFlashWord_E440(dev, Param, (u16 *)ibuf->outBuffer); break;
            case E140: ReadFlashWord_E140(dev, Param, (u16 *)ibuf->outBuffer); break;
            case E2010B:
            case E2010: ldev_e2010_read_plata_descr(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16));break; // all at once
            case E154: ReadFlashWord_E154(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16)); // all at once            
         }
         status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
      } break;

      case DIOC_WRITE_FLASH_WORD:
      {
         DbgPrint("In DIOC_WRITE_FLASH_WORD \n");
         Param = *(u16 *)ibuf->inBuffer;
         switch(dev->Type)
         {
            case E440: WriteFlashWord_E440(dev, Param, *(u16 *)ibuf->outBuffer); break;
            case E140: WriteFlashWord_E140(dev, Param, *(u16 *)ibuf->outBuffer); break;
            case E2010B:
            case E2010: e2010_write_plate_descr(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16));break; // all at once
            case E154: WriteFlashWord_E154(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16)); // all at once
         }
         status = 0;
      } break;

      case DIOC_ENABLE_FLASH_WRITE:
      {
         DbgPrint("In DIOC_ENABLE_FLASH_WRITE \n");
         Param = *(u16 *)ibuf->outBuffer;
         switch(dev->Type)
         {
            case E440: EnableFlashWrite_E440(dev, Param); break;
            case E140: EnableFlashWrite_E140(dev, Param); break;
            case E154: EnableFlashWrite_E154(dev, Param); break;
            case E2010B:
            case E2010: break;
         }
         status = 0;
      } break;

      case DIOC_COMMAND_PLX: {
            DbgPrint("DIOC_COMMAND_PLX \n");
            Param = *(u16 *)ibuf->inBuffer;
            switch(dev->Type)
            {
               case E440: COMMAND_E440(dev, Param); break;
               case E140: COMMAND_E140(dev, Param);break;
               case E154: COMMAND_E154(dev, Param);break;
            }
            status = 0;
         } break;

      case DIOC_GET_DM_A: {    // METHOD_OUT_DIRECT
            DbgPrint("DIOC_GET_DM_A \n");
            Param = *(u16 *)ibuf->inBuffer;
            switch(dev->Type)
            {
               case E440: GET_DATA_MEMORY_E440(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16), Param); break;
               case E140: GET_DATA_MEMORY_E140(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16), Param); break;
               case E154: GET_DATA_MEMORY_E154(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16), Param); break;
            }
            status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));;
         } break;

      case DIOC_PUT_DM_A: {   // METHOD_IN_DIRECT
            DbgPrint("DIOC_PUT_DM_A \n");
            Param = *(u16 *)ibuf->inBuffer;
            switch(dev->Type)
            {
               case E440: PUT_DATA_MEMORY_E440(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16), Param); break;
               case E140: PUT_DATA_MEMORY_E140(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16), Param); break;
               case E154: PUT_DATA_MEMORY_E154(dev, (u16 *)ibuf->outBuffer, ibuf->outSize/sizeof(u16), Param); break;
            }
            status = 0;
         } break;

      case DIOC_GET_PM_A: {    // METHOD_OUT_DIRECT
            DbgPrint("DIOC_GET_PM_A \n");
            Param = *(u16 *)ibuf->inBuffer;
            switch(dev->Type)
            {
               case E440: GET_PM_MEMORY_E440(dev, (u32 *)ibuf->outBuffer, ibuf->outSize/sizeof(u32), Param); break;
               case E140: *((u8 *)ibuf->outBuffer) = GET_PM_WORD_E140(dev, Param); break;
               case E154: *((u8 *)ibuf->outBuffer) = GET_PM_WORD_E154(dev, Param); break;
            }
            status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
         } break;

      case DIOC_PUT_PM_A: {   // METHOD_IN_DIRECT
            DbgPrint("DIOC_PUT_PM_A \n");
            Param = *(u16 *)ibuf->inBuffer;
            switch(dev->Type)
            {
               case E440: PUT_PM_MEMORY_E440(dev, (u32 *)ibuf->outBuffer, ibuf->outSize/sizeof(u32), Param); break;
               case E140: PUT_PM_WORD_E140(dev, Param, *((u8 *)ibuf->outBuffer)); break;
               case E154: PUT_PM_WORD_E154(dev, Param, *((u8 *)ibuf->outBuffer)); break;
            }
            status = 0;
         } break;

      case DIOC_ADCSAMPLE: // for e-140 specific...
      {
         DbgPrint("In DIOC_ADCSAMPLE \n");
         if(test_bit(FLAG_WORKING,&dev->dev_flags)) break;
         switch(dev->Type)
         {
         case E140:
            {
               Param1 = (u16)*((u32 *)ibuf->inBuffer);
               Param = MAKE_E140CHAN(Param1);

               ldevusb_enable(dev,0);

               PUT_DM_WORD_E140(dev,L_ADC_CHANNEL_SELECT_E140, Param);
               udelay(1000);
               PUT_DM_WORD_E140(dev,L_ADC_CHANNEL_SELECT_E140, Param);
               *((u32 *)ibuf->outBuffer) = GET_DM_WORD_E140(dev,L_ADC_SAMPLE_E140);
            } break;
            
         case E154:
            {
               Param = (u16)*((u32 *)ibuf->inBuffer);
               ldevusb_enable(dev,0);
               PUT_DM_WORD_E154(dev,L_ADC_CHANNEL_SELECT_E154, Param);
               *((u32 *)ibuf->outBuffer) = GET_DM_WORD_E154(dev,L_ADC_SAMPLE_E154);
            } break;
         }            
         status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
      } break;

    case DIOC_DAC_OUT: // for e20-10 specific...
        if ((dev->Type == E2010) || (dev->Type == E2010B)) {
            Param = *(u16 *)ibuf->inBuffer;
            status = ldev_e2010_put_dm_word(dev,SEL_DAC_DATA, Param);
        } else {
            status = -ENOTTY;
        }
        break;
    case DIOC_TTL_OUT: // for e20-10 specific...
        if ((dev->Type == E2010) || (dev->Type == E2010B)) {
            Param = *(u16 *)ibuf->inBuffer;
            status = ldev_e2010_put_dm_word(dev,SEL_DIO_DATA, Param);
        }
        break;
    case DIOC_TTL_CFG: // for e20-10 specific...
        if ((dev->Type == E2010) || (dev->Type == E2010B)) {
            Param = *(u16 *)ibuf->inBuffer;
            status = ldev_e2010_put_pm_word(dev,SEL_DIO_PARAM, (u8)Param);
        }
        break;
    case DIOC_TTL_IN:  // for e20-10 specific...
        if ((dev->Type == E2010) || (dev->Type == E2010B)) {
            DbgPrint("In DIOC_TTL_IN \n");
            status = ldev_e2010_get_dm_word(dev, SEL_DIO_DATA, ((u16 *)ibuf->outBuffer));
            if (status == 0)
                status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
        } else {
            status = -ENOTTY;
        }
        break;
    case DIOC_ENABLE_CORRECTION: // for e20-10B specific...
        if ((dev->Type == E2010) || (dev->Type == E2010B)) {
            if (ibuf->inSize < (int)sizeof(AdcCoef)) {
                status = -EINVAL;
            } else  {
                memcpy(AdcCoef, ibuf->inBuffer, sizeof(AdcCoef));
                status = ldev_e2010_put_pm_word(dev, ADC_CORRECTION_ADDRESS, 1);
                if (status == 0) {
                    status = ldev_e2010_put_data_memory(dev, (u8*)AdcCoef, sizeof(AdcCoef),
                                                        SEL_ADC_CALIBR_KOEFS);
                }
            }
        } else {
            status = -ENOTTY;
        }
        break;
      case DIOC_SETBUFFER:  // METHOD_OUT_DIRECT
      {
         DbgPrint("In SETBUFFER \n");
         if(test_bit(FLAG_WORKING,&dev->dev_flags)) break;

         dev->wBufferSize = *(u32 *)ibuf->inBuffer;
         DbgPrint("Set Buffer %d \n", dev->wBufferSize);

         /* unmark the pages reserved */
         if(dev->Sync) { hb_free(dev->Sync); }

         dev->RealPages =dev->wBufferSize/2048+(dev->wBufferSize%2048?1:0);
         //dev->Sync = vmalloc((dev->RealPages+1)*PAGE_SIZE);
         dev->Sync = hb_malloc(dev->RealPages+1);
         if(dev->Sync==NULL)
             break;
         dev->SyncCntr = (volatile int *)dev->Sync->addr[0];

         DbgPrint("Set real Buffer %d \n", dev->RealPages*2048);
         *(u32 *)ibuf->outBuffer = (dev->RealPages)*2048; // +1 in dll for mapping pagecount page
         status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
      } break;

      case DIOC_SETUP: // METHOD_OUT_DIRECT - get ADC_PAR return UserData
      {
         DbgPrint("In SETUP \n");
         if(!dev->Sync) break;
         if(test_bit(FLAG_WORKING,&dev->dev_flags)) break;

         if(ibuf->inSize>sizeof(WDAQ_PAR)) break;
         memcpy(&dev->adcPar,ibuf->inBuffer,ibuf->inSize);

         if((dev->Type!=E2010)&&(dev->Type!=E2010B)) // E140 E440
         {
            dev->wFIFO=dev->adcPar.t3.FIFO;
            dev->wIrqStep = dev->adcPar.t3.IrqStep;
            dev->wPages = dev->adcPar.t3.Pages;
            DbgPrint(" %x %x %x %x \n",dev->adcPar.t3.Rate,dev->adcPar.t3.NCh,dev->adcPar.t3.Chn[0],dev->adcPar.t3.FIFO);
         }
         else // E2010
         {
            dev->wFIFO=dev->adcPar.t4.FIFO;
            dev->wIrqStep = dev->adcPar.t4.IrqStep;
            dev->wPages = dev->adcPar.t4.Pages;
            DbgPrint(" %x %x %x %x \n",dev->adcPar.t4.Rate,dev->adcPar.t4.NCh,dev->adcPar.t4.Chn[0],dev->adcPar.t4.FIFO);
         }

         if(dev->wIrqStep==0) break;

         DbgPrint("Set Buffer %d %d \n", dev->wPages, dev->wIrqStep);

         SetBoardFIFO(dev);// - УСТАНВЛИВАЕТ ФИФО на плате(( case внутри функции

         if(dev->wPages>(dev->RealPages*2048L/dev->wIrqStep)) dev->wPages=(dev->RealPages*2048/dev->wIrqStep);

         ((u32 *)ibuf->outBuffer)[0] = dev->wPages;  // used size
         ((u32 *)ibuf->outBuffer)[1] = dev->wFIFO;
         ((u32 *)ibuf->outBuffer)[2] = dev->wIrqStep;
         ((u32 *)ibuf->outBuffer)[3] = 0xA5A5A5A5;
         ibuf->outSize=4*sizeof(u32);

         status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
      } break;

      case DIOC_INIT_SYNC:
      {
         DbgPrint("In INIT_SYNC \n");
         if(!dev->Sync) break;
         if(test_bit(FLAG_WORKING,&dev->dev_flags)) break;
         hb_setel(dev->Sync,0,int,0);
         //if(d->DacBuf) { ((u32 *)d->DacBuf)[0]=0; ((u32 *)d->DacBuf)[1]=0; }
         status = 0;
      } break;

    case DIOC_START: {
            int i;
            status = 0;
            if(!dev->Sync) {
                status = -EFAULT;
            } else if (test_bit(FLAG_WORKING,&dev->dev_flags)) {
                status = -EFAULT;
            }
            if (status == 0) {
                set_bit(FLAG_WORKING,&dev->dev_flags);

                for (i = 0; (i < LDEV_IO_TRANSF_CNT) && (status == 0); i++) {
                    status = ldev_io_alloc(&dev->rx_ctx.transfs[i], dev->udev,
                                           usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
                                           dev->wIrqStep);
                }

                if (status == 0)
                    status = ldevusb_enable(dev, 1);

                if (status == 0) {
                    dev->io_thread
                            = kthread_create(ldev_io_thread_func, dev, "ldevusb");
                    if (IS_ERR(dev->io_thread)) {
                        status = -EFAULT;
                        dev->io_thread = NULL;
                    } else {
                        get_task_struct(dev->io_thread);
                        wake_up_process(dev->io_thread);
                    }
                }

                if (status != 0) {
                    ldevusb_stop(dev);
                }
            }
        }
        break;
      case DIOC_STOP:
         status = ldevusb_stop(dev);
         break;
      case DIOC_OUTM: {
            PORT_PAR par;
            if(ibuf->inSize != sizeof(PORT_PAR))
                break;
            memcpy(&par,ibuf->inBuffer,ibuf->inSize);
            if ((dev->Type == E2010) || (dev->Type == E2010B)) {
                status = ldev_e2010_put_data_memory(dev, ibuf->outBuffer, ibuf->outSize, par.port);
                DbgPrint("OUTM addr 0x%08X, size = %d, status %d\n", par.port, ibuf->outSize, status);
            }
        }
            break;
        case DIOC_INM:  {
            PORT_PAR par;
            if(ibuf->inSize != sizeof(PORT_PAR))
                break;
            memcpy(&par,ibuf->inBuffer,ibuf->inSize);
            if ((dev->Type == E2010) || (dev->Type == E2010B)) {
                status = ldev_e2010_get_data_memory(dev, ibuf->outBuffer, ibuf->outSize, par.port);
                DbgPrint("INM addr 0x%08X, size = %d, status %d\n", par.port, ibuf->outSize, status);
            } else {
                status = -ENOTTY;
            }

            if (status == 0) {
                status = copy_to_user((void*)arg, ibuf, sizeof(IOCTL_BUFFER));
            }
        }
        break;
        default:
        DbgPrint("Unknown IOCTL !!! \n");
        status = -ENOTTY;
    }

   mutex_unlock(&dev->io_lock);
   kfree(ibuf);
   return status;
}

int ldevusb_enable(ldevusb *dev, int flWork) {
    int status = 0;
   if (flWork) {
      //dev->CurPage=0;
      //DacCurPage = 0;
   }

   switch(dev->Type)  {
       case E440:  EnableE440(dev,flWork); break;
       case E140:  EnableE140(dev,flWork); break;
       case E154:  EnableE154(dev,flWork); break;

       case E2010B:
       case E2010:
            status = ldev_e2010_enable(dev,flWork);
            break;
   }
   return status;
}


static int ldevusb_mmap(struct file *filp, struct vm_area_struct *vma) {
    ldevusb *dev = (ldevusb *)filp->private_data;
    int status;
    long length = vma->vm_end - vma->vm_start;
    unsigned long start = vma->vm_start;
    u8 *vmalloc_area_ptr = NULL;
    unsigned long pfn;
    int pages;
    int i=0;

    DbgPrint("In MMAP IOCTL !!! \n");
    DbgPrint("offset field %ld", vma->vm_pgoff);
    if (!dev->Sync)
        return -ENOMEM;

    if(!(vma->vm_flags & VM_READ))
        return -EINVAL;

    //if(vma->vm_flags & VM_WRITE) { vmalloc_area_ptr = dev->OutBulk; DbgPrint("map out buf");}
    //else
    {
        vmalloc_area_ptr = (u8 *)dev->Sync->addr[i];
        pages = dev->RealPages+1;
        DbgPrint("map in buf");
    }
    /* check length - do not allow larger mappings than the number of pages allocated */
    if(length > (pages*PAGE_SIZE))
        return -EINVAL;

    vma->vm_flags|=VM_IO;

    /* loop over all pages, map it page individually */
    while (length > 0)  {
        //pfn = __pa((void *)vmalloc_area_ptr) >> PAGE_SHIFT;
        pfn = page_to_pfn(virt_to_page(vmalloc_area_ptr));
        if((status = remap_pfn_range(vma, start, pfn, PAGE_SIZE, PAGE_SHARED))<0)
            return status;
        start += PAGE_SIZE;
        vmalloc_area_ptr = (u8 *)dev->Sync->addr[++i];
        length -= PAGE_SIZE;
    }

    return 0;
}


/////////////////////////////////////////////////////////////
// MAIN part
/////////////////////////////////////////////////////////////
/* table of devices that work with this driver */
static struct usb_device_id ldriver_table [] = {
   { USB_DEVICE(0x0471, 0x0440) },
   { USB_DEVICE(0x0471, 0x0140) },
   { USB_DEVICE(0x0471, 0x2010) },
   { USB_DEVICE(0x0471, 0x0154) },   
   {/*            vid     pid */} /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, ldriver_table);

static struct file_operations fops = {
   .owner =   THIS_MODULE,
   .open =    ldevusb_open,
   .unlocked_ioctl =   ldevusb_ioctl,
   .mmap =    ldevusb_mmap,
   .release = ldevusb_release,
};

static struct usb_driver ldriver = {
   .name = device_name,
   .id_table = ldriver_table,
   .probe = ldriver_probe,
   .disconnect = ldriver_disconnect,
};

static int __init ldriver_init(void)
{
   int status = usb_register(&ldriver);
   if(status) DbgPrint("usb_register failed. Error number %d \n", status);
   return status;
}

static void __exit ldriver_exit(void)
{
   usb_deregister(&ldriver);
}

module_init (ldriver_init);
module_exit (ldriver_exit);
