#include <linux/types.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/pci.h>

#include "sony_fpga.h"
#include "sony_fpga_netif.h"

#define LOCAL_ID_TO_SLOT_ID(x)  (((x) >> 0) & 0xf)
#define LOCAL_ID_TO_BIM_ID(x)   (((x) >> 4) & 0xf)

#define SLOT_ID_R3  0x9
#define SLOT_ID_R5  0xA

#define BIM_ID_A    0x0
#define BIM_ID_B    0x1

static const struct sony_fpga_ctrl* net_fpga_ctrl[SONY_FPGA_NETIF_FPGA_ID_MAX] = { 0 };

static bool get_fpga_id(enum SONY_FPGA_NETIF_FPGA_ID* fpga_id, const struct sony_fpga_ctrl* ctrl)
{
    u32 bus_no;
    u32 fpga_kind;
    u32 slot_id;
    u32 bim_id;

    BUG_ON(!fpga_id);
    BUG_ON(!ctrl);

    bus_no = ctrl->pci_dev->bus->number;
    fpga_kind = FPGA_KIND(ctrl->fpga_uniq_id);
    slot_id = LOCAL_ID_TO_SLOT_ID(ctrl->local_id);
    bim_id = LOCAL_ID_TO_BIM_ID(ctrl->local_id);

    dev_info(&ctrl->pci_dev->dev, "%s: vendor=0x%x, device=0x%x, bus_no=0x%02x, fpga_kind=0x%x, "
             "slot_id=0x%x, bim_id=0x%x\n",
             __func__,
             ctrl->pci_dev->vendor,
             ctrl->pci_dev->device,
             bus_no,
             fpga_kind,
             slot_id, bim_id);

    if (ctrl->pci_dev->vendor == SONY_FPGA_VID) {
        if (slot_id == SLOT_ID_R3) {
            if (ctrl->pci_dev->device == SONY_FPGA_DID_NEM) {
                *fpga_id = SONY_FPGA_NETIF_FPGA_ID_NET1_NEM;
                return true;
            }
            if (ctrl->pci_dev->device == SONY_FPGA_DID_BIM) {
                if (bim_id == BIM_ID_A) {
                    *fpga_id = SONY_FPGA_NETIF_FPGA_ID_NET1_BIMA;
                    return true;
                }
                if (bim_id == BIM_ID_B) {
                    *fpga_id = SONY_FPGA_NETIF_FPGA_ID_NET1_BIMB;
                    return true;
                }
            }
        }
        else if (slot_id == SLOT_ID_R5) {
            if (ctrl->pci_dev->device == SONY_FPGA_DID_NEM) {
                *fpga_id = SONY_FPGA_NETIF_FPGA_ID_NET2_NEM;
                return true;
            }
            if (ctrl->pci_dev->device == SONY_FPGA_DID_BIM) {
                if (bim_id == BIM_ID_A) {
                    *fpga_id = SONY_FPGA_NETIF_FPGA_ID_NET2_BIMA;
                    return true;
                }
                if (bim_id == BIM_ID_B) {
                    *fpga_id = SONY_FPGA_NETIF_FPGA_ID_NET2_BIMB;
                    return true;
                }
            }
        }
    }

    return false;
}

static const char* get_fpga_name(enum SONY_FPGA_NETIF_FPGA_ID fpga_id)
{
    switch (fpga_id) {
    case SONY_FPGA_NETIF_FPGA_ID_NET1_NEM:  return "NEM";
    case SONY_FPGA_NETIF_FPGA_ID_NET1_BIMA: return "BIMA";
    case SONY_FPGA_NETIF_FPGA_ID_NET1_BIMB: return "BIMB";
    case SONY_FPGA_NETIF_FPGA_ID_NET2_NEM:  return "NEM";
    case SONY_FPGA_NETIF_FPGA_ID_NET2_BIMA: return "BIMA";
    case SONY_FPGA_NETIF_FPGA_ID_NET2_BIMB: return "BIMB";
    default:
        return "Unknown";
    }
}

void sony_fpga_netif_notify_probe_success(const struct sony_fpga_ctrl* ctrl)
{
    enum SONY_FPGA_NETIF_FPGA_ID fpga_id;

    if (!ctrl) {
        return;
    }

    if (!get_fpga_id(&fpga_id, ctrl)) {
        return;
    }

    if (fpga_id >= ARRAY_SIZE(net_fpga_ctrl)) {
        return;
    }

    net_fpga_ctrl[fpga_id] = ctrl;
}

bool sony_fpga_netif_is_ioready(enum SONY_FPGA_NETIF_FPGA_ID fpga_id)
{
    const struct sony_fpga_ctrl* ctrl;

    if (fpga_id >= ARRAY_SIZE(net_fpga_ctrl)) {
        return false;
    }
    ctrl = net_fpga_ctrl[fpga_id];

    if (!ctrl) {
        return false;
    }

    return true;
}
EXPORT_SYMBOL(sony_fpga_netif_is_ioready);

void sony_fpga_netif_iowrite32(u32 data, enum SONY_FPGA_NETIF_FPGA_ID fpga_id, u32 bar_index, u32 offset)
{
    const struct sony_fpga_ctrl* ctrl;
    u32 __iomem* addr;

    if (!sony_fpga_netif_is_ioready(fpga_id)) {
        return;
    }
    ctrl = net_fpga_ctrl[fpga_id];

    BUILD_BUG_ON(
        ARRAY_SIZE(ctrl->bar) !=
        ARRAY_SIZE(ctrl->bar_size));

    if (bar_index >= ARRAY_SIZE(ctrl->bar)) {
        return;
    }

    addr = (u32 __iomem*)(ctrl->bar[bar_index]);
    if (!addr) {
        return;
    }

    if (offset >= ctrl->bar_size[bar_index]) {
        return;
    }

    IO_WRITE_32(data, addr + (offset / sizeof(u32)));
}
EXPORT_SYMBOL(sony_fpga_netif_iowrite32);

u32 sony_fpga_netif_ioread32(enum SONY_FPGA_NETIF_FPGA_ID fpga_id, u32 bar_index, u32 offset)
{
    const struct sony_fpga_ctrl* ctrl;
    u32 __iomem* addr;

    if (!sony_fpga_netif_is_ioready(fpga_id)) {
        return 0;
    }
    ctrl = net_fpga_ctrl[fpga_id];

    BUILD_BUG_ON(
        ARRAY_SIZE(ctrl->bar) !=
        ARRAY_SIZE(ctrl->bar_size));

    if (bar_index >= ARRAY_SIZE(ctrl->bar)) {
        return 0;
    }

    addr = (u32 __iomem*)(ctrl->bar[bar_index]);
    if (!addr) {
        return 0;
    }

    if (offset >= ctrl->bar_size[bar_index]) {
        return 0;
    }

    return IO_READ_32(addr + (offset / sizeof(u32)));
}
EXPORT_SYMBOL(sony_fpga_netif_ioread32);

int sony_fpga_netif_request_irq(enum SONY_FPGA_NETIF_FPGA_ID fpga_id, irq_handler_t handler, void* dev)
{
    const struct sony_fpga_ctrl* ctrl;
    int rc;

    if (!sony_fpga_netif_is_ioready(fpga_id)) {
        return 0;
    }
    ctrl = net_fpga_ctrl[fpga_id];

    rc = request_irq(ctrl->pci_dev->irq, handler, IRQF_SHARED, get_fpga_name(fpga_id), dev);
    if (rc) {
        dev_err(&ctrl->pci_dev->dev, "request irq error (rc=%d)", rc);
        return rc;
    }

    return rc;
}
EXPORT_SYMBOL(sony_fpga_netif_request_irq);

void sony_fpga_netif_free_irq(enum SONY_FPGA_NETIF_FPGA_ID fpga_id, void* dev)
{
    const struct sony_fpga_ctrl* ctrl;

    if (!sony_fpga_netif_is_ioready(fpga_id)) {
        return;
    }
    ctrl = net_fpga_ctrl[fpga_id];

    free_irq(ctrl->pci_dev->irq, dev);
}
EXPORT_SYMBOL(sony_fpga_netif_free_irq);

void* sony_fpga_netif_get_dmac_buffer(enum SONY_FPGA_NETIF_FPGA_ID fpga_id)
{
    if (fpga_id < ARRAY_SIZE(net_fpga_ctrl) && net_fpga_ctrl[fpga_id]) {
        return net_fpga_ctrl[fpga_id]->ctrl_dmac_buffer;
    }
    return NULL;
}
EXPORT_SYMBOL(sony_fpga_netif_get_dmac_buffer);

extern vd_wait_queue_t vd_wait_queue;
extern u32 int_counter;
long sony_fpga_netif_wait_dma_ready(void)
{
    u32 counter;
    long ret = 0;
    do {
        counter = int_counter;

        ret = vd_wait_event_interruptible(vd_wait_queue, int_counter != counter);
        if (ret != 0) {
            return ret;
        }
        counter = int_counter;
    } while ((counter % 4) != 1); // TODO DMA参照タイミングはVD同期以外いつでもよい
    return 0;
}
EXPORT_SYMBOL(sony_fpga_netif_wait_dma_ready);
