#include <linux/spi/spi.h>
#include "bcm_mirror.h"
#include "spi_mpc.h"
#include "mirror_drv.h"
#include "spbx_log/spbx_log.h"

#define MIR_PORT_PAG  (0x3)
#define MIR_CTL_REG   (0x10)
#define MIR_CAP_PORT_REG (0x18)

#define EN_OUT_MIR_FLTR (1<<4)
#define EN_IN_MIR_FLTR  (1<<5)

#define MIR_CAP_PORT(x)  (1 <<(x))

#define SW_READ_MODE  (0x60)
#define SW_WRITE_MODE (0x61)

#define SW_SPI_DATA_REG(x)  (0xF0 + (x))
#define SW_SPI_STATUS_REG   (0xFE)
#define SW_SPI_PAGE_REG     (0xFF)

#define SW_STATUS_SPIF    (1<<7)
#define SW_STATUS_WCOL    (1<<6)
#define SW_STATUS_RACK    (1<<5)
#define SW_STATUS_MDIO_ST (1<<2)
#define SW_STATUS_RXRDY   (1<<1)
#define SW_STATUS_TXRDY   (0<<1)
#define SW_MAX_TIME       (10)

#define MAX_PORT  (28)

static unsigned char g_page_addr = 0;

static int sw_get_spi_status(unsigned char* status)
{
    char temp_rx[3];
    char temp_tx[3];
    struct spi_transfer t;
    struct spi_message m;
    struct spi_device* spi = DRV_GetDrvDetail()->pSPIDevice;

    memset(temp_rx, 0, 3);
    memset(temp_tx, 0, 3);

    temp_tx[0] = SW_READ_MODE;
    temp_tx[1] = SW_SPI_STATUS_REG;
    temp_tx[2] = 0x0;

    spi_message_init(&m);

    t.tx_buf = temp_tx;
    t.rx_buf = temp_rx;
    t.len = 3;
    spi_message_add_tail(&t, &m);

    if(spi_sync(spi, &m) < 0)
    {
        printk(KERN_ERR "RD error in spi operation\n");
        return -1;
    }

    *status = temp_rx[2];

    return 0;
}

static int sw_set_page_addr(unsigned char page)
{
    struct spi_device* spi = DRV_GetDrvDetail()->pSPIDevice;
    char temp_rx[3];
    char temp_tx[3];
    struct spi_transfer t;
    struct spi_message m;

    memset(temp_rx, 0, 3);
    memset(temp_tx, 0, 3);

    temp_tx[0] = SW_WRITE_MODE;
    temp_tx[1] = SW_SPI_PAGE_REG;
    temp_tx[2] = page;

    spi_message_init(&m);

    t.tx_buf = temp_tx;
    t.rx_buf = temp_rx;
    t.len = 3;

    spi_message_add_tail(&t, &m);

    if(spi_sync(spi, &m) < 0)
    {
        printk(KERN_ERR "RD error in spi operation\n");
        return -1;
    }

    return 0;
}

static int dummy_read(PBCM_REGS pRegs)
{
    struct spi_device* spi = DRV_GetDrvDetail()->pSPIDevice;
    unsigned char len = 3;
    char temp_rx[3];
    char temp_tx[3];
    struct spi_transfer t;
    struct spi_message  m;

    memset(temp_rx , 0, len);
    memset(temp_tx , 0, len);

    temp_tx[0] = SW_READ_MODE;
    temp_tx[1] = pRegs->reg_addr;
    temp_tx[2] = 0;

    spi_message_init(&m);
    t.tx_buf = temp_tx;
    t.rx_buf = temp_rx;
    t.len = 3;
    spi_message_add_tail(&t, &m);

    if(spi_sync(spi, &m) < 0)
    {
        printk(KERN_ERR "RD error in spi operation\n");
        return -1;
    }

    return 0;
}

static unsigned int sw_read_reg(PBCM_REGS pRegs)
{
    unsigned int index = 0;
    unsigned char status;
    unsigned char page_addr = pRegs->page_addr;
    struct spi_device* spi = DRV_GetDrvDetail()->pSPIDevice;

    if(spi == NULL || pRegs == NULL)
    {
        return -1;
    }

    do
    {

        if(sw_get_spi_status(&status))
        {
            status = -1;
            index++;
        }

    }
    while((status & SW_STATUS_SPIF) != 0 && index < SW_MAX_TIME);


    if(index >= SW_MAX_TIME)
    {
        printk("status :0x%x,SPIF !=0\n", status);
        return -1;
    }

    if(page_addr != g_page_addr)
    {
        unsigned char page_addr = pRegs->page_addr;

        if(!sw_set_page_addr(page_addr))
        {
            g_page_addr = page_addr;
        }
        else
        {
            printk("page addr set fail\n");
            return -1;
        }
    }

    if(!dummy_read(pRegs))
    {
        do
        {
            if(sw_get_spi_status(&status))
            {
                status = -1;
                index++;
            }

        }
        while(((status & SW_STATUS_RACK) == 0) && (index < SW_MAX_TIME));


        if(index >= SW_MAX_TIME)
        {
            printk("status :0x%x,SPIF !=0\n", status);
            return -1;
        }

        do
        {
            int i;
            unsigned char len  = pRegs->len;
            char temp_rx[len + 2];
            char temp_tx[len + 2];
            struct spi_transfer t;
            struct spi_message  m;

            memset(temp_rx , 0, len + 2);
            memset(temp_tx , 0, len + 2);

            temp_tx[0] = SW_READ_MODE;
            temp_tx[1] = SW_SPI_DATA_REG(0);

            spi_message_init(&m);
            t.tx_buf = temp_tx;
            t.rx_buf = temp_rx;
            t.len = len + 2;
            spi_message_add_tail(&t, &m);

            if(spi_sync(spi, &m) < 0)
            {
                printk(KERN_ERR "RD error in spi operation\n");
                return -1;
            }

            for(i = 0; i < len; i++)
            {
                pRegs->buf[i] = temp_rx[len + 2 - 1 - i];

            }
        }
        while(0);
    }
    else
    {
        printk("dummy_read fail\n");
        return -1;
    }

    return 0;
}

static int sw_write_reg(PBCM_REGS pRegs)
{
    unsigned int index = 0;
    unsigned char status;
    unsigned char page_addr = pRegs->page_addr;
    struct spi_device* spi = DRV_GetDrvDetail()->pSPIDevice;

    if(spi == NULL || pRegs == NULL)
    {
        return -1;
    }

    do
    {

        if(sw_get_spi_status(&status))
        {
            status = -1;
            index++;
        }

    }
    while((status & SW_STATUS_SPIF) != 0 && (index < SW_MAX_TIME));

    if(index >= SW_MAX_TIME)
    {
        printk("status :0x%x,SPIF !=0\n", status);
        return -1;
    }

//  printk("page_addr:0x%x,g_page_addr:0x%x\n",page_addr,g_page_addr);

    if(page_addr != g_page_addr)
    {
        unsigned char page_addr = pRegs->page_addr;

        if(!sw_set_page_addr(page_addr))
        {
            g_page_addr = page_addr;
        }
        else
        {
            printk("page addr set fail\n");
            return -1;
        }
    }

    do
    {
        int i;
        unsigned char len  = pRegs->len;
        char temp_rx[len + 2];
        char temp_tx[len + 2];
        struct spi_transfer t;
        struct spi_message  m;

        memset(temp_rx , 0, len + 2);
        memset(temp_tx , 0, len + 2);

        temp_tx[0] = SW_WRITE_MODE;
        temp_tx[1] = pRegs->reg_addr;

        for(i = 0; i < len; i++)
        {
            //   printk("value[%d] = 0x%x\n",i,pRegs->buf[i]);
            temp_tx[2 + i] = pRegs->buf[len - 1 - i];
        }

        spi_message_init(&m);
        t.tx_buf = temp_tx;
        t.rx_buf = temp_rx;
        t.len = len + 2;
        spi_message_add_tail(&t, &m);

        if(spi_sync(spi, &m) < 0)
        {
            printk(KERN_ERR "RD error in spi operation\n");
            return -1;
        }
    }
    while(0);

    return 0;
}

static int bcm_mir_disable(void)
{
    BCM_REGS bcmRegs = {0};
    PBCM_REGS pRegs = &bcmRegs;

    memset(pRegs, 0, sizeof(BCM_REGS));

    pRegs->page_addr = MIR_PORT_PAG;

    pRegs->reg_addr = MIR_CAP_PORT_REG;
    pRegs->len = 4;
    pRegs->buf[0] = 0;
    pRegs->buf[1] = 0;
    pRegs->buf[2] = 0;
    pRegs->buf[3] = 0;
    sw_write_reg(pRegs);
    sw_read_reg(pRegs);

    pRegs->reg_addr = EG_MIR_PORT_REG;
    pRegs->len = 4;
    pRegs->buf[0] = 0;
    pRegs->buf[1] = 0;
    pRegs->buf[2] = 0;
    pRegs->buf[3] = 0;
    sw_write_reg(pRegs);
    sw_read_reg(pRegs);

    pRegs->reg_addr = ING_MIR_PORT_REG;
    pRegs->len = 4;
    pRegs->buf[0] = 0;
    pRegs->buf[1] = 0;
    pRegs->buf[2] = 0;
    pRegs->buf[3] = 0;
    sw_write_reg(pRegs);
    sw_read_reg(pRegs);

    pRegs->reg_addr = MIR_CTL_REG;
    pRegs->len = 1;
    pRegs->buf[0] = 0;
    sw_write_reg(pRegs);
    sw_read_reg(pRegs);

    return (0);
}
static int bcm_mir_access_on(unsigned int src, unsigned int dst, MIR_REG off)
{
    BCM_REGS bcmRegs = {0};
    PBCM_REGS pRegs = &bcmRegs;

    if(src >= MAX_PORT || dst >= MAX_PORT)
    {
        return 0;
    }

    memset(pRegs, 0, sizeof(BCM_REGS));

    pRegs->page_addr = MIR_PORT_PAG;

    pRegs->reg_addr = MIR_CAP_PORT_REG;
    pRegs->len = 4;
    sw_read_reg(pRegs);

    pRegs->buf[0] |= (MIR_CAP_PORT(dst) >> 24) & 0xff;
    pRegs->buf[1] |= (MIR_CAP_PORT(dst) >> 16) & 0xff;
    pRegs->buf[2] |= (MIR_CAP_PORT(dst) >> 8) & 0xff;
    pRegs->buf[3] |= (MIR_CAP_PORT(dst) >> 0) & 0xff;
    sw_write_reg(pRegs);

    pRegs->buf[0] = 0;
    pRegs->buf[1] = 0;
    pRegs->buf[2] = 0;
    pRegs->buf[3] = 0;

    sw_read_reg(pRegs);

#if 0
    printk("R:PAGE:%d,REG:%d,VALUE:0x%x 0x%x 0x%x 0x%x\n",
           pRegs->page_addr,
           pRegs->reg_addr,
           pRegs->buf[0],
           pRegs->buf[1],
           pRegs->buf[2],
           pRegs->buf[3]);
#endif

    pRegs->reg_addr = off ;
    pRegs->len = 4;
    sw_read_reg(pRegs);

    pRegs->buf[0] |= (MIR_CAP_PORT(src) >> 24) & 0xff;
    pRegs->buf[1] |= (MIR_CAP_PORT(src) >> 16) & 0xff;
    pRegs->buf[2] |= (MIR_CAP_PORT(src) >> 8) & 0xff;
    pRegs->buf[3] |= (MIR_CAP_PORT(src) >> 0) & 0xff;
    sw_write_reg(pRegs);


    pRegs->buf[0] = 0;
    pRegs->buf[1] = 0;
    pRegs->buf[2] = 0;
    pRegs->buf[3] = 0;

    sw_read_reg(pRegs);

/*
    printk("R:PAGE:%d,REG:%d,VALUE:0x%x 0x%x 0x%x 0x%x\n",
           pRegs->page_addr,                              
           pRegs->reg_addr,                               
           pRegs->buf[0],                                 
           pRegs->buf[1],                                 
           pRegs->buf[2],                                 
           pRegs->buf[3]);                                
*/

    pRegs->reg_addr = MIR_CTL_REG;
    pRegs->len = 1;

    sw_read_reg(pRegs);

    pRegs->buf[0] |= (off == ING_MIR_PORT_REG ?
                      EN_IN_MIR_FLTR :
                      EN_OUT_MIR_FLTR);
    sw_write_reg(pRegs);

    pRegs->buf[0] = 0;
    sw_read_reg(pRegs);

    //printk("R:PAGE:%d,REG:%d,VALUE:0x%x\n", pRegs->page_addr, pRegs->reg_addr, pRegs->buf[0]);

    return 0;
}

static int bcm_mir_access_off(unsigned int fe, MIR_REG off)
{
    BCM_REGS bcmRegs = {0};
    PBCM_REGS pRegs = &bcmRegs;

    if(fe >= MAX_PORT)
    {
        return 0;
    }

    memset(pRegs, 0, sizeof(BCM_REGS));

    pRegs->page_addr = MIR_PORT_PAG;
    pRegs->reg_addr = off;
    pRegs->len = 4;
    sw_read_reg(pRegs);

    pRegs->buf[0] &= ~((MIR_CAP_PORT(fe) >> 24) & 0xff);
    pRegs->buf[1] &= ~((MIR_CAP_PORT(fe) >> 16) & 0xff);
    pRegs->buf[2] &= ~((MIR_CAP_PORT(fe) >> 8) & 0xff);
    pRegs->buf[3] &= ~((MIR_CAP_PORT(fe) >> 0) & 0xff);
    sw_write_reg(pRegs);

    pRegs->buf[0] = 0;
    pRegs->buf[1] = 0;
    pRegs->buf[2] = 0;
    pRegs->buf[3] = 0;

    sw_read_reg(pRegs);


/*
    printk("R:PAGE:%d,REG:%d,VALUE:0x%x 0x%x 0x%x 0x%x\n",
           pRegs->page_addr,                              
           pRegs->reg_addr,                               
           pRegs->buf[0],                                 
           pRegs->buf[1],                                 
           pRegs->buf[2],                                 
           pRegs->buf[3]);                                
*/

    return 0;

}

int bcm_mir_out_on(unsigned int src, unsigned int dst)
{
    return bcm_mir_access_on(src, dst, ING_MIR_PORT_REG);
}

int bcm_mir_in_on(unsigned int src, unsigned int dst)
{
    return bcm_mir_access_on(src, dst, EG_MIR_PORT_REG);
}

int bcm_mir_out_off(unsigned int fe)
{
    return bcm_mir_access_off(fe,  ING_MIR_PORT_REG);
}

int bcm_mir_in_off(unsigned int fe)
{
    return bcm_mir_access_off(fe,  EG_MIR_PORT_REG);
}