#include <linux/cdev.h>
#include <linux/module.h>
#include "./sony_fpga.h"
#include "./sony_fpga_sysfs.h"

/* sysfsから読み書きするページ番号 (とりあえずドライバ単位にする。) */
static ulong video_descriptor_page = 0;

static ssize_t process_status_show(struct device* dev, struct device_attribute* attr, char* buf);
static ssize_t fpga_reg_show(struct device* dev, struct device_attribute* attr, char* buf);
static ssize_t fpga_version_show(struct device* dev, struct device_attribute* attr, char* buf);
static ssize_t report_ch1_show(struct device* dev, struct device_attribute* attr, char* buf);
static ssize_t report_ch1_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);
static ssize_t report_ch2_show(struct device* dev, struct device_attribute* attr, char* buf);
static ssize_t report_ch2_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);

static ssize_t ctrl_data_show(struct device* dev, struct device_attribute* attr, char* buf);
static ssize_t ctrl_data_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);

static ssize_t ctrl_data_debug_show(struct device* dev, struct device_attribute* attr, char* buf);
static ssize_t ctrl_data_debug_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);

static ssize_t video_descriptor_show(struct device* dev, struct device_attribute* attr, char* buf);
static ssize_t file_dmac_descriptor_show(struct device* dev, struct device_attribute* attr, char* buf);

/* attribute */
static DEVICE_ATTR(process_status        , 0444, process_status_show, NULL);
static DEVICE_ATTR(fpga_reg              , 0444, fpga_reg_show, NULL);
static DEVICE_ATTR(fpga_version          , 0444, fpga_version_show, NULL);
static DEVICE_ATTR(report_ch1            , 0444, report_ch1_show, report_ch1_store);
static DEVICE_ATTR(report_ch2            , 0444, report_ch2_show, report_ch2_store);
static DEVICE_ATTR(ctrl_data             , 0444, ctrl_data_show, ctrl_data_store);
static DEVICE_ATTR(ctrl_data_debug       , 0444, ctrl_data_debug_show, ctrl_data_debug_store);
static DEVICE_ATTR(video_descriptor      , 0444, video_descriptor_show, NULL);
static DEVICE_ATTR(file_dmac_descriptor  , 0444, file_dmac_descriptor_show, NULL);
static DEVICE_ULONG_ATTR(video_descriptor_page , 0664, video_descriptor_page);

static struct attribute* attrs[] = {
    &dev_attr_process_status.attr,
    &dev_attr_fpga_reg.attr,
    &dev_attr_fpga_version.attr,
    &dev_attr_report_ch1.attr,
    &dev_attr_report_ch2.attr,
    &dev_attr_ctrl_data.attr,
    &dev_attr_ctrl_data_debug.attr,
    &dev_attr_video_descriptor.attr,
    &dev_attr_file_dmac_descriptor.attr,
    &dev_attr_video_descriptor_page.attr.attr,
    NULL
};

struct attribute_group attr_group = {
    .attrs = attrs,
};

#define BUFF_PRINT(len, buf, size, fmt, ...) {\
    len = scnprintf(buf, size, fmt, ##__VA_ARGS__); \
    size -= len; \
    buf += len; \
}

inline void buff_printf(char* buf, int size, const char* fmt, ...)
{

}

static ssize_t process_status_show(struct device* dev, struct device_attribute* attr, char* buf)
{
    int len;
    int size = PAGE_SIZE;

    if (process_status_area == NULL) {
        return 0;
    }
    BUFF_PRINT(len, buf, size, "%x", *process_status_area);
    return PAGE_SIZE-size;
}

static ssize_t fpga_reg_show(struct device* dev, struct device_attribute* attr, char* buf)
{
    struct sony_fpga_ctrl* ctrl_data = (struct sony_fpga_ctrl*)dev_get_drvdata(dev);
    int len;
    void __iomem* addr;
    int size = PAGE_SIZE;
    int i;

    if (ctrl_data == NULL) {
        printk(KERN_DEBUG "ctrl_data not assigned");
        return 0;
    }

    BUFF_PRINT(len, buf, size, "<FPGA_REG_DUMP>\n");

    addr = ctrl_data->bar[FPGA_CTRL_BAR];

    for (i = 0 ; i < sizeof(struct sony_fpga_register) / sizeof(u32); i++) {
        if (i % COLUMNS == 0) {
            BUFF_PRINT(len, buf, size, "\n0x%08lx :", (i * sizeof(u32)));
        }

        BUFF_PRINT(len, buf, size, " 0x%08x", IO_READ_32((u8 __iomem*)addr + i * sizeof(u32)));

        if (size <= 4) break;
    }
    size -= scnprintf(buf, size, "\n");

    return PAGE_SIZE-size;
}

static ssize_t fpga_version_show(struct device* dev, struct device_attribute* attr, char* buf)
{
    struct sony_fpga_ctrl* ctrl_data = (struct sony_fpga_ctrl*)dev_get_drvdata(dev);
    void __iomem* addr;
    int size = PAGE_SIZE;
    u32 readdata;
    version_info* ver;
    int len;
    u32 date;

    if (ctrl_data == NULL) {
        printk(KERN_DEBUG "ctrl_data not assigned");
        return 0;
    }

    BUFF_PRINT(len, buf, size, "<FPGA_VERSION_DUMP>\n");

    addr  = ctrl_data->bar[FPGA_CTRL_BAR];

    readdata = IO_READ_32((u8 __iomem*)addr + offsetof(struct sony_fpga_register, pcie_version));
    ver = (version_info*)&readdata;
    BUFF_PRINT(len, buf, size, "pcie_version: %02x.%02x_%02x\n", ver->major_version, ver->minor_version, ver->debug_version);
    date = IO_READ_32((u8 __iomem*)addr + offsetof(struct sony_fpga_register, pcie_date));
    BUFF_PRINT(len, buf, size, "pcie_date: %04x-%02x-%02x 00:00\n", DATE_2_YEAR(date), DATE_2_MONTH(date), DATE_2_DAY(date));
    readdata = IO_READ_32((u8 __iomem*)addr + offsetof(struct sony_fpga_register, fpga_version));
    ver = (version_info*)&readdata;
    BUFF_PRINT(len, buf, size, "fpga_version: %02x.%02x_%02x\n", ver->major_version, ver->minor_version, ver->debug_version);
    date = IO_READ_32((u8 __iomem*)addr + offsetof(struct sony_fpga_register, fpga_date));
    BUFF_PRINT(len, buf, size, "fpga_date: %04x-%02x-%02x 00:00\n", DATE_2_YEAR(date), DATE_2_MONTH(date), DATE_2_DAY(date));
    BUFF_PRINT(len, buf, size, "fpga_type: %s\n", FPGA_TYPE(ctrl_data->fpga_uniq_id));
    return PAGE_SIZE-size;
}

static ssize_t video_descriptor_show(struct device* dev, struct device_attribute* attr, char* buf)
{
    struct sony_fpga_ctrl* ctrl_data = (struct sony_fpga_ctrl*)dev_get_drvdata(dev);
    char* addr = (char*)ctrl_data->video_descriptor_virt_addr + video_descriptor_page * PAGE_SIZE;

    memcpy(buf, addr, PAGE_SIZE);
    return PAGE_SIZE-1;
}

static ssize_t file_dmac_descriptor_show(struct device* dev, struct device_attribute* attr, char* buf)
{
    struct sony_fpga_ctrl* ctrl_data = (struct sony_fpga_ctrl*)dev_get_drvdata(dev);
    char* addr = (char*)ctrl_data->file_dmac_descriptor_virt_buffer + video_descriptor_page * PAGE_SIZE;

    memcpy(buf, addr, PAGE_SIZE);
    return PAGE_SIZE-1;
}

static ssize_t report_ch_show(struct device* dev, struct device_attribute* attr, char* buf, u32 bank)
{
    struct sony_fpga_ctrl* ctrl_data = NULL;
    int i, idx, len, size;
    struct report_data* report = NULL;
    ctrl_data = (struct sony_fpga_ctrl*)dev_get_drvdata(dev);

    size = PAGE_SIZE;
    idx = ctrl_data->report_idx[bank] % 100 - 1;
    if (idx < 0) idx = 99;

    BUFF_PRINT(len, buf, size, "<ch1 dma report>\n");
    BUFF_PRINT(len, buf, size, "[idx] [etime] [page_num] [direction] [target]\n");

    for (i = 0; i < 100; i++) {
        report = &(ctrl_data->report_data[bank][idx]);
        BUFF_PRINT(len, buf, size, "[%d],%d,%d,%c,%s, %lx\n", idx, report->etime, report->page_num,
                (report->direction == 0) ? 'R' : 'W' ,
                report->target,
                report->delta.tv_usec + report->delta.tv_sec * 1000000);
        idx--;
        if (idx < 0) {
           idx = 99;
        }
    }
    return PAGE_SIZE - size;
}
static ssize_t report_ch1_show(struct device* dev, struct device_attribute* attr, char* buf)
{
    return report_ch_show(dev, attr, buf, 0);
}
static ssize_t report_ch2_show(struct device* dev, struct device_attribute* attr, char* buf)
{
    return report_ch_show(dev, attr, buf, 1);
}

static ssize_t report_ch1_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count)
{
    return count;
}
static ssize_t report_ch2_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count)
{
    return count;
}

static ssize_t ctrl_data_show(struct device* dev, struct device_attribute* attr, char* buf)
{
    u32* ctrl_data = (u32*)dev_get_drvdata(dev);

    memcpy(buf, ctrl_data, sizeof(struct sony_fpga_ctrl));
    return sizeof(struct sony_fpga_ctrl);
}
static ssize_t ctrl_data_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count)
{
    return count;
}

#define BUFSIZE 2000
static ssize_t ctrl_data_debug_show(struct device* dev, struct device_attribute* attr, char* buf)
{
    struct sony_fpga_ctrl* ctrl_data = (struct sony_fpga_ctrl*)dev_get_drvdata(dev);

    char a_buff[BUFSIZE];
    char* buff;
    int size = BUFSIZE;
    int len;
    buff = a_buff;

    BUFF_PRINT(len, buff, size, "<ctrl_data_ptr_display>\n");

    BUFF_PRINT(len, buff, size, "%llx\n", (u64)ctrl_data);
    BUFF_PRINT(len, buff, size, "free_descriptor: %p(%llu)\n", ctrl_data->free_video_descriptor,
            ((u64)ctrl_data->free_video_descriptor - (u64)ctrl_data->video_descriptor_virt_addr) / sizeof(struct dma_desc));

    BUFF_PRINT(len, buff, size, "free_descriptor_cnt: %d\n", ctrl_data->free_video_descriptor_cnt);

    BUFF_PRINT(len, buff, size, "-- offset information --\n");

    BUFF_PRINT(len, buff, size, "cdevno(%lx) cdev(%lx) bar(%lx) bar_size(%lx)  report_data(%lx) report_idx(%lx)\n",
            offsetof(struct sony_fpga_ctrl, cdevno),
            offsetof(struct sony_fpga_ctrl, cdev),
            offsetof(struct sony_fpga_ctrl, bar),
            offsetof(struct sony_fpga_ctrl, bar_size),
            offsetof(struct sony_fpga_ctrl, report_data),
            offsetof(struct sony_fpga_ctrl, report_idx));

    strncat(buf, a_buff, BUFSIZE-size);
    return BUFSIZE-size;
}
static ssize_t ctrl_data_debug_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count)
{
    return count;
}

MODULE_LICENSE("Dual BSD/GPL");
