#include <linux/init.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/version.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/proc_fs.h>
#include <linux/pci.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/poll.h>


#include "lpcie_version.h"
#include "lpcie_trace.h"
#include "lpcie_drv.h"
#include "lpcie_streams.h"
#include "l502_fpga_regs.h"



MODULE_AUTHOR( "Borisov Alexey <borisov@lcard.ru>" );
MODULE_LICENSE( "GPL v2" );
MODULE_DESCRIPTION( "Driver for L Card PCI-Express data acquisition boards" );
#ifdef MODULE_SUPPORTED_DEVICE
MODULE_SUPPORTED_DEVICE( "l-502" );
#endif
MODULE_VERSION(LPCIE_DRIVER_VERSION);


#define L502_DEVICE_ID  0x0502
#define L502_VENDOR_ID  0x1172

/** имя драйвера при его регистрации */
#define DRIVER_NAME "lpcie"
/** с этой строки начинаются названия ссылок на устройства в /dev */
#define DEVICE_NAME  "lpcie"
/** название класса устройств в /sys/class */
#define CLASS_NAME   "lpcie"


#define MAX_DEVICES_CNT    255








/*  ------------------- определение функций -------------------------------- */
static __init int f_lpcie_init(void);
static void f_lpcie_exit(void);

module_init(f_lpcie_init);
module_exit(f_lpcie_exit);

#ifndef __devinit
    #define __devinit
#endif

#ifndef __devexit
    #define __devexit
    #define __devexit_p
#endif

/* добавление/удаление устройства */
static __devinit int f_lpcie_probe(struct pci_dev *pci_dev, const struct pci_device_id *dev_id);
static __devexit void f_lpcie_remove(struct pci_dev *pci_dev);
static void f_lpcie_free_dev(t_lpcie_dev_context* dev);


/* обработчики прерывания */
static irqreturn_t f_lpcie_msi_isr(int irq, void *dev_id);
static irqreturn_t f_lpcie_isr(int irq, void *dev_id);
static void f_work_func(struct work_struct *work);

/* функции операций с файлом устройства */
int lpcie_create_spec_files(t_lpcie_dev_context* dev);
int lpcie_open(struct inode *inode, struct file *pfile);
int lpcie_release(struct inode *inode, struct file *pfile);
ssize_t lpcie_read(struct file *pfile, char __user *buff, size_t count, loff_t *poffs);
ssize_t lpcie_write(struct file *pfile, const char __user *buff, size_t count, loff_t *poffs);
unsigned int lpcie_poll (struct file *pfile, struct poll_table_struct *wait);

int lpcie_ioctl(struct inode *inode, struct file *pfile, unsigned int cmd, unsigned long arg);
long lpcie_ioctl_unlock(struct file *pfile, unsigned int cmd, unsigned long arg);


/** Таблица с идентификаторами поддерживаемых устройств */
static struct pci_device_id f_pci_devs_ids[] = {
    {PCI_DEVICE(L502_VENDOR_ID, L502_DEVICE_ID)},
    {0,}
};
MODULE_DEVICE_TABLE(pci, f_pci_devs_ids);


/** Структура созданного класса устройств */
static struct class *f_class=0;
/** Старший номер драйвера для создания ссылок в /dev */
static dev_t f_major_num = 0;
/** Признак, что драйвер был зарегистрирован */
static int f_pci_driver_register = 0;
/** список найденных драйвером устройств */
static LIST_HEAD(f_devlist);
/** spinlock для эксклюзивного доступа к списку устройств */
static DEFINE_SPINLOCK(f_devlist_lock);


/** структура задет операции для работы с файлом устройства */
static const struct file_operations f_ops = {
    .owner = THIS_MODULE,

    .open = lpcie_open,
    .release = lpcie_release,
    .read = lpcie_read,
    .write = lpcie_write,
    .poll = lpcie_poll,


#if (LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11))
    .ioctl = lpcie_ioctl,  /**< вариант ioctl с BKL (ядро <= 2.6.10) */
#else
    .unlocked_ioctl = lpcie_ioctl_unlock, /**< вариант ioctl без внешних блокировок (ядро >= 2.6.11) */
#endif
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11))
    .compat_ioctl = lpcie_ioctl_unlock, /**< вариант ioctl при вызове 64-битного
                                              драйвера из 32-битного приложения.
                                              если размер параметров не зависит от разрядности,
                                              то можно использовать ту же функцию */
#endif
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 12, 0)
    .llseek = no_llseek /** lseek не поддерживается */
#endif
};



/** структура с информацией о драйвере PCI */
static struct pci_driver f_pci_driver = {
    .name = DRIVER_NAME,
    .id_table = f_pci_devs_ids,
    .probe = f_lpcie_probe,
    .remove = __devexit_p(f_lpcie_remove)
};









/** Инициализация драйвера */
static __init int f_lpcie_init(void) {
    int err = 0;

    PDEBUG("Load driver. Linux kernel version = %d.%d.%d\n", (LINUX_VERSION_CODE>>16) & 0xFF,
         (LINUX_VERSION_CODE>>8) & 0xFF, LINUX_VERSION_CODE & 0xFF);

    /* выделяем номера под устройства */
    err = alloc_chrdev_region(&f_major_num, 0, MAX_DEVICES_CNT, DEVICE_NAME);
    if (err) {
        f_major_num = 0;
        PERROR("alloc dev number error = %d\n", err);
    } else {
        f_major_num = MAJOR(f_major_num);
    }


    /* регистрируем класс устройств */
    if (!err) {
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 4, 0)
        f_class = class_create(THIS_MODULE, CLASS_NAME);
#else
        f_class = class_create(CLASS_NAME);
#endif


        if (IS_ERR(f_class)) {
            err = PTR_ERR(f_class);
            PERROR("Class registration error!\n");
            f_class = 0;
        } else {
            PDEBUG("Register class succesfully");
        }
    }


    /* регистрируем pci-драйвер в системе */
    if (!err) {
        err = pci_register_driver(&f_pci_driver);
        if (err) {
            PERROR("pci driver register error: %d\n", err);
        } else {
            f_pci_driver_register = 1;
            PDEBUG("register pci driver successfully\n");
        }
    }


    /* если была ошибка - освобождаем выделенные ресурсы */
    if (err)
        f_lpcie_exit();

    return err;
}

/** освобождение выделенных драйвером ресурсов */
static void f_lpcie_exit(void) {
    //struct list_head *cur, *next;

    /*
    struct lpcie_dev* dev;

    spin_lock(&f_devlist_lock);
    list_for_each_safe(cur, next, &f_devlist)
    {
        dev = list_entry(cur, struct lpcie_dev, list);        
        f_lpcie_free_dev(dev,1);
    }
    spin_unlock(&f_devlist_lock); */


    /* отменяем регистрацию драйвера, если он был зарегистрирован */
    if (f_pci_driver_register)
        pci_unregister_driver(&f_pci_driver);

    /* удаляем класс устройств */
    if (f_class) {
        class_destroy(f_class);
        f_class = 0;
    }

    /* овобождаем номера для драйверов */
    if (f_major_num)
        unregister_chrdev_region(MKDEV(f_major_num,0), MAX_DEVICES_CNT);
}


static unsigned int f_allocate_devnum(void) {
    static unsigned int f_max_num = 0;
    return f_max_num++;
}

/** Функция вызывается для проверки, принадлежит ли устройство драйверу.
  Необходимо по ID проверить устройство и вернуть 0, проинициализировава устройство */
static __devinit int f_lpcie_probe(struct pci_dev *pci_dev, const struct pci_device_id *dev_id) {
    int err = 0;
    t_lpcie_dev_context* dev = 0;

    PDEBUG("new device found. probing...\n");

    /* выделяем память под контекст устройства */
    dev = kmalloc(sizeof(t_lpcie_dev_context), GFP_KERNEL);
    if (dev) {
        memset(dev, 0, sizeof(t_lpcie_dev_context));
        /* инициализация полей */
        dev->pid = dev_id->device;
        dev->vid = dev_id->vendor;

        dev->devnum = MKDEV(f_major_num, f_allocate_devnum());

        snprintf(dev->devname, sizeof(dev->devname), "lpcie%d", MINOR(dev->devnum));

        atomic_set(&dev->is_rdy, 1);


        /* разрешаем работу с устройством */
        err = pci_enable_device(pci_dev);
        if (!err) {
            dev->pci_dev = pci_dev;
            pci_set_drvdata(pci_dev, dev);
        } else {
            PERROR("Cannot enable pci device! err = %d", err);
        }


        /* запрашиваем адреса BAR для работы с устройством */
        if (!err) {
            dev->fpga_regs.start = pci_resource_start(dev->pci_dev, 0);
            dev->fpga_regs.len = pci_resource_len(dev->pci_dev,0);
            if (request_mem_region(dev->fpga_regs.start, dev->fpga_regs.len, dev->devname)) {
                dev->fpga_regs.buf = ioremap(dev->fpga_regs.start , dev->fpga_regs.len);
                if (!dev->fpga_regs.buf) {
                    PERROR("Cannot remap fpga regs region!\n");
                    release_mem_region(dev->fpga_regs.start, dev->fpga_regs.len);
                    err = -EBUSY;
                }
            } else {
                PERROR("Cannot request fpga regs region!\n");
                err = -EBUSY;
            }
        }

        /* разрешаем DMA на 64 бита, если не удалось, то возвращаем на 32 */
        if (!err) {
            if (!dma_set_mask(&dev->pci_dev->dev, DMA_BIT_MASK(64))) {
                dma_set_coherent_mask(&dev->pci_dev->dev, DMA_BIT_MASK(64));
                PDEBUG("64-bit dma enabled\n");
            } else if (!dma_set_mask(&dev->pci_dev->dev, DMA_BIT_MASK(32))) {
                dma_set_coherent_mask(&dev->pci_dev->dev, DMA_BIT_MASK(32));
                PDEBUG("32-bit dma enabled\n");
            } else {
                PERROR("No suitable DMA available!!\n");
                err = -EFAULT;
            }
        }


        /* захватываем прерывание и устанавливаем обработчик */
        if (!err) {
            /* изначально убеждаемся, что все прерывания в устройстве запрещены
               и нет отсавшихся флагов прерывания */

            err = lpcie_fpga_reg_write(dev, L502_REGS_DMA_IRQ_DIS, 0xFFFFFFFF);
            if (!err)
                err = lpcie_fpga_reg_write(dev, L502_REGS_DMA_IRQ, 0xFFFFFFFF);
            if (!err) {
                int msi;


                /* если есть возможность, используем MSI-прерывание */
                msi = pci_enable_msi(dev->pci_dev);
                if (msi  == 0) {
                    PDEBUG("Allocate msi irq\n");
                    dev->use_msi = 1;
                } else {
                    PDEBUG("msi allocation faild. use INTA interrupt...\n");
                }

                /* устанавливаем обработчик прерывания (для msi другой,
                    так как в нем не нужно проверять, от какого устройства
                    пришло прерывание) */
                err = request_irq(dev->pci_dev->irq,
                                  dev->use_msi ? f_lpcie_msi_isr : f_lpcie_isr,
                                  dev->use_msi ? 0 : IRQF_SHARED,
                                  dev->devname, dev);
                if (err) {
                    PERROR("Request irq error: %d!\n", err);
                } else {
                    dev->use_irq = 1;
                }
            }
        }

        /* читаем информацию из EEPROM */
        if (!err) {
            lpcie_read_descr(dev);
        }


        if (!err) {
            /* убираем сигнал сброса для BlackFin, чтобы можно было
               подключится к нему по JTAG при желании */
            uint32_t reg;
            err = lpcie_fpga_reg_read(dev, L502_REGS_BF_CTL, &reg);
            if (!err) {
                reg |= L502_REGBIT_BF_CTL_BF_RESET_Msk;
                err = lpcie_fpga_reg_write(dev, L502_REGS_BF_CTL, reg);
            }
        }

        if (!err) {
            //tasklet_init(&dev->work, f_work_func, (unsigned long)dev);
            dev->workqueue = create_singlethread_workqueue(dev->devname);
            INIT_WORK(&dev->work, f_work_func);
            spin_lock_init(&dev->irq_lock);
        }

        if (!err) {
            /* инициализация потоков DMA */
            err = lpcie_streams_init(dev);
        }

        /* создаем объект символьного устройства, чтобы с устройством
         * можно было бы взаимодействовать из пользовательской программы */
        if (!err) {
            cdev_init(&dev->cdev, &f_ops);
            dev->cdev.owner = THIS_MODULE;
            err = cdev_add(&dev->cdev, dev->devnum, 1);
            if (err) {
                PERROR("cdev_add error: %d for device %d\n", err, MINOR(dev->devnum));
                dev->devnum  = 0;
            } else {
                PDEBUG("create device: %d, %d\n",
                       MAJOR(dev->devnum), MINOR(dev->devnum));
            }
        }

        /* создаем объект устройства, доступный в /sys/class/lpcie.
           Затем уже udev должен создать ссылку в /dev */
        if (!err) {

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
            dev->devclass = device_create(f_class, NULL, dev->devnum,
                dev, dev->devname);
#else
            dev->devclass = device_create(f_class, NULL, dev->devnum, dev->devname);
#endif
            if (IS_ERR(dev->devclass)) {
                err = PTR_ERR(dev->devclass);
                dev->devclass = 0;
                PERROR("class device %d.%d create error!! %d\n",
                    MAJOR(dev->devnum), MINOR(dev->devnum), err);
            }
        }

        if (!err)
            err = lpcie_create_spec_files(dev);

        /* добавляем устойство в список устройств */
        if (!err) {
            spin_lock(&f_devlist_lock);
            list_add_tail(&dev->list, &f_devlist);
            dev->in_list = 1;
            spin_unlock(&f_devlist_lock);
        }

        if (err) {
            f_lpcie_free_dev(dev);
            dev = 0;
        }
    } else {
        err = -ENOMEM;
    }

    return err;
}

static __devexit void f_lpcie_remove(struct pci_dev *pci_dev) {
    t_lpcie_dev_context* dev = (t_lpcie_dev_context*)pci_get_drvdata(pci_dev);
    PDEBUG("remove device\n");
    if (dev)
        f_lpcie_free_dev(dev);
}


static void f_lpcie_free_dev(t_lpcie_dev_context* dev) {
    if (dev->workqueue) {
        destroy_workqueue(dev->workqueue);
        dev->workqueue = 0;
    }
    /* удаляем устройства из класса устройств lpcie */
    if (dev->devclass) {
        device_destroy(f_class, dev->devnum);
        dev->devclass = 0;
    }

    if (dev->devnum) {
        /* удаляем символьное устройство */
        cdev_del(&dev->cdev);
        PDEBUG("remove device %d,%d", MAJOR(dev->devnum), MINOR(dev->devnum));
    }

    if (dev->use_irq) {
        free_irq(dev->pci_dev->irq, dev);
        dev->use_irq = 0;
    }


    /* освобождаем выделенные вектора MSI, если использовали */
    if (dev->use_msi) {
        pci_disable_msi(dev->pci_dev);
        dev->use_msi = 0;
    }

    /* освобождаем буферы для доступа к областями PCI-утсройства */
    if (dev->fpga_regs.buf) {
        iounmap(dev->fpga_regs.buf);
        dev->fpga_regs.buf = 0;
        release_mem_region(dev->fpga_regs.start, dev->fpga_regs.len);
    }


    /* запрещаем работу с устройством */
    if (dev->pci_dev) {
        /* запрещаяем работу в режиме мастера шины */
        pci_clear_master(dev->pci_dev);
        /* запрещаем само устройство */
        pci_disable_device(dev->pci_dev);
    }


    /* удаляем из списка */
    if (dev->in_list) {
        spin_lock(&f_devlist_lock);
        list_del(&dev->list);
        spin_unlock(&f_devlist_lock);
    }

    /* удаляем саму структуру */
    kfree(dev);
}








/***************************************************************************//**
  Обработчик прерывания, без проверки dev_id. Используется на прямую в MSI,
  так как MSI выделяется единолично для каждого устройства
  *****************************************************************************/
static irqreturn_t f_lpcie_msi_isr(int irq, void *dev_id)
{
    irqreturn_t ret = IRQ_NONE;
    uint32_t irq_en=0, irq_state=0;
    t_lpcie_dev_context* dev = (t_lpcie_dev_context*) dev_id;

    /* запрещаем все прерывания, чтобы не потерять MSI прерывание, если
     * прерывания произойдут подряд, и второе произойдет после чтения
     * статуса, но до сброса прерываний
     */
    lpcie_fpga_reg_read(dev, L502_REGS_DMA_IRQ_EN, &irq_en);
    lpcie_fpga_reg_write(dev, L502_REGS_DMA_IRQ_DIS, irq_en);


    /* читаем регистр, указывающий, какие прерывания произошли */
    lpcie_fpga_reg_read(dev, L502_REGS_DMA_IRQ, &irq_state);
    if (irq_state)
    {
        /* если были прерывания - то сбрасываем их, и устанавливаем признак,
          что прерывание было наше */
        ret = IRQ_HANDLED;
        lpcie_fpga_reg_write(dev, L502_REGS_DMA_IRQ, irq_state);
        dev->irq_state |= irq_state;

        queue_work(dev->workqueue, &dev->work);
    }
    lpcie_fpga_reg_write(dev, L502_REGS_DMA_IRQ_EN, irq_en);


    return ret;
}



/***************************************************************************//**
  Обработчик прерывания для обычного ISR.
  Должны определить, действительно ли это одно из устройств из поддерживаемых,
  только если это так, то вызываем обработчик
  *****************************************************************************/
static irqreturn_t f_lpcie_isr(int irq, void *dev_id)
{
    struct list_head *ptr;
    t_lpcie_dev_context *dev=0, *lst_dev;
    irqreturn_t ret = IRQ_NONE;

    spin_lock(&f_devlist_lock);
    list_for_each(ptr, &f_devlist)
    {
        lst_dev = list_entry(ptr, t_lpcie_dev_context, list);
        if (lst_dev == (t_lpcie_dev_context*) dev_id)
        {
            dev = lst_dev;
        }
    }
    spin_unlock(&f_devlist_lock);

    if (dev)
    {
        ret = f_lpcie_msi_isr(irq, dev_id);
    }
    return ret;
}



static void f_work_func(struct work_struct *work)
{
    t_lpcie_dev_context* dev = (t_lpcie_dev_context*)container_of(work, t_lpcie_dev_context, work);
    unsigned long flags;
    uint32_t irq_state;

    spin_lock_irqsave(&dev->irq_lock, flags);

    irq_state = dev->irq_state;
    dev->irq_state = 0;
    spin_unlock_irqrestore(&dev->irq_lock, flags);

    PDEBUG("work occured: 0x%x", irq_state);

    lpcie_streams_proc_irq(dev, irq_state);
}



int lpcie_fpga_reg_write(t_lpcie_dev_context* dev, u32 reg, u32 val)
{
    reg <<= 2;
    if ((reg > dev->fpga_regs.len) || !dev->fpga_regs.buf)
        return -EFAULT;
    iowrite32(val, dev->fpga_regs.buf + reg);
    return 0;
}

int lpcie_fpga_reg_read(t_lpcie_dev_context* dev, u32 reg, u32* val)
{
    reg <<= 2;
    if ((reg > dev->fpga_regs.len) || !dev->fpga_regs.buf)
        return -EFAULT;
    *val = ioread32(dev->fpga_regs.buf + reg);
    return 0;
}



