/* 
 * Cisco router simulation platform.
 * Copyright (c) 2007 Christophe Fillot (cf@utc.fr)
 *
 * C6k-Sup1a Midplane FPGA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cpu.h"
#include "vm.h"
#include "dynamips.h"
#include "memory.h"
#include "device.h"
#include "nmc93cX6.h"
#include "dev_c6sup1.h"

#define DEBUG_UNKNOWN  1
#define DEBUG_ACCESS   1
#define DEBUG_NET_IRQ  1

/* 
 * Function 0xX000:
 *   bit 0: 0:present, 1:absent.
 *   bit 1: power ok (?)
 */
#define SLOT_NOT_PRESENT   0x01
#define SLOT_POWER_OK      0x02

/* 
 * Function 0xX200: requires bit 3 to be set to avoid error about power 
 * convertor failure.
 */
#define SLOT_POWER_CONVERTOR   0x08

/* Midplane FPGA private data */
struct c6sup1_mpfpga_data {
   vm_obj_t vm_obj;
   struct vdevice dev;

   c6sup1_t *router;
   m_uint32_t irq_status;
   m_uint32_t intr_enable;

   /* Slot/function selector */
   u_int slot_sel;

   /* Slot status (up/down) */
   u_int slot_status[C6SUP1_MAX_SLOTS];
};

/* === Definitions for "Backplane" EEPROM (Chassis Clock, VTT, ...) ======= */
#define EEPROM_BP_DOUT  0      /* reg 0x3c */
#define EEPROM_BP_DIN   0      /* reg 0x20 */
#define EEPROM_BP_CLK   1

/* Chip select (CS) bits */
#define EEPROM_BP_CS_CHASSIS   3   /* Chassis (6509,...) */
#define EEPROM_BP_CS_CHASSIS2  4   /* Chassis redundant EEPROM ? */
#define EEPROM_BP_CS_PS1       5   /* Power Supply #1 */
#define EEPROM_BP_CS_PS2       6   /* Power Supply #2 */
#define EEPROM_BP_CS_CLK1      7   /* Clock card #1 */
#define EEPROM_BP_CS_CLK2      8   /* Clock card #2 */
#define EEPROM_BP_CS_VTT1      9   /* VTT #1 */
#define EEPROM_BP_CS_VTT2      10  /* VTT #2 */
#define EEPROM_BP_CS_VTT3      11  /* VTT #3 */

static const struct nmc93cX6_eeprom_def eeprom_bp_def_chassis = {
   EEPROM_BP_CLK, EEPROM_BP_CS_CHASSIS, EEPROM_BP_DIN, EEPROM_BP_DOUT,
};

static const struct nmc93cX6_eeprom_def eeprom_bp_def_chassis2 = {
   EEPROM_BP_CLK, EEPROM_BP_CS_CHASSIS2, EEPROM_BP_DIN, EEPROM_BP_DOUT,
};

static const struct nmc93cX6_eeprom_def eeprom_bp_def_ps1 = {
   EEPROM_BP_CLK, EEPROM_BP_CS_PS1, EEPROM_BP_DIN, EEPROM_BP_DOUT,
};

static const struct nmc93cX6_eeprom_def eeprom_bp_def_ps2 = {
   EEPROM_BP_CLK, EEPROM_BP_CS_PS2, EEPROM_BP_DIN, EEPROM_BP_DOUT,
};

static const struct nmc93cX6_eeprom_def eeprom_bp_def_clk1 = {
   EEPROM_BP_CLK, EEPROM_BP_CS_CLK1, EEPROM_BP_DIN, EEPROM_BP_DOUT,
};

static const struct nmc93cX6_eeprom_def eeprom_bp_def_clk2 = {
   EEPROM_BP_CLK, EEPROM_BP_CS_CLK2, EEPROM_BP_DIN, EEPROM_BP_DOUT,
};

static const struct nmc93cX6_eeprom_def eeprom_bp_def_vtt1 = {
   EEPROM_BP_CLK, EEPROM_BP_CS_VTT1, EEPROM_BP_DIN, EEPROM_BP_DOUT,
};

static const struct nmc93cX6_eeprom_def eeprom_bp_def_vtt2 = {
   EEPROM_BP_CLK, EEPROM_BP_CS_VTT2, EEPROM_BP_DIN, EEPROM_BP_DOUT,
};

static const struct nmc93cX6_eeprom_def eeprom_bp_def_vtt3 = {
   EEPROM_BP_CLK, EEPROM_BP_CS_VTT3, EEPROM_BP_DIN, EEPROM_BP_DOUT,
};

/* Backplane EEPROMs */
static const struct nmc93cX6_group eeprom_bp_group = {
   EEPROM_TYPE_NMC93C56, 9, 0, 
   EEPROM_DORD_REVERSED,
   EEPROM_DOUT_KEEP,
   EEPROM_DEBUG_DISABLED,
   "Backplane EEPROMs",
   {
      &eeprom_bp_def_chassis,
      &eeprom_bp_def_chassis2, 
      &eeprom_bp_def_ps1,
      &eeprom_bp_def_ps2,
      &eeprom_bp_def_clk1,
      &eeprom_bp_def_clk2,
      &eeprom_bp_def_vtt1,
      &eeprom_bp_def_vtt2,
      &eeprom_bp_def_vtt3,
   },
};

/* === Definitions for "Supervisor" EEPROMs (Sup1A,PFC/EARL) ============== */
#define EEPROM_SUP_DOUT    0         /* XXX */
#define EEPROM_SUP_DIN     2
#define EEPROM_SUP_CLK     1
#define EEPROM_SUP_CS      3

#define EEPROM_EARL_DOUT   2         /* XXX */
#define EEPROM_EARL_DIN    9
#define EEPROM_EARL_CLK    10
#define EEPROM_EARL_CS     8

static const struct nmc93cX6_eeprom_def eeprom_sup_def = {
   EEPROM_SUP_CLK, EEPROM_SUP_CS, EEPROM_SUP_DIN, EEPROM_SUP_DOUT,
};

static const struct nmc93cX6_eeprom_def eeprom_earl_def = {
   EEPROM_EARL_CLK, EEPROM_EARL_CS, EEPROM_EARL_DIN, EEPROM_EARL_DOUT,
};

/* Supervisor EEPROMs */
static const struct nmc93cX6_group eeprom_sup_group = {
   EEPROM_TYPE_NMC93C56, 2, 0, 
   EEPROM_DORD_REVERSED,
   EEPROM_DOUT_KEEP,
   EEPROM_DEBUG_DISABLED,
   "Supervisor EEPROMs",
   { &eeprom_sup_def, &eeprom_earl_def },
};

/* === Definitions for "Slot" EEPROM ====================================== */
#define EEPROM_SLOT_DOUT   0   /* reg 0x4c */
#define EEPROM_SLOT_DIN    0   /* reg 0x48 */
#define EEPROM_SLOT_CLK    1
#define EEPROM_SLOT_CS     3

static const struct nmc93cX6_eeprom_def eeprom_slot_def = {
   EEPROM_SLOT_CLK, EEPROM_SLOT_CS, EEPROM_SLOT_DIN, EEPROM_SLOT_DOUT,
};

static const struct nmc93cX6_group eeprom_slot_group = {
   EEPROM_TYPE_NMC93C56, 1, 0, 
   EEPROM_DORD_REVERSED,
   EEPROM_DOUT_KEEP,
   EEPROM_DEBUG_DISABLED,
   "Slot EEPROMs",
   { &eeprom_slot_def },
};

/* ------------------------------------------------------------------------ */

/* Update network interrupt status */
static inline 
void dev_c6sup1_mpfpga_net_update_irq(struct c6sup1_mpfpga_data *d)
{
   if (d->irq_status) {
      vm_set_irq(d->router->vm,C6SUP1_NETIO_IRQ);
   } else {
      vm_clear_irq(d->router->vm,C6SUP1_NETIO_IRQ);
   }
}

/* Trigger a Network IRQ for the specified slot/port */
void dev_c6sup1_mpfpga_net_set_irq(struct c6sup1_mpfpga_data *d,
                                   u_int slot,u_int port)
{
#if DEBUG_NET_IRQ
   vm_log(d->router->vm,"MP_FPGA","setting NetIRQ for slot %u port %u\n",
          slot,port);
#endif
   d->irq_status |= 1 << slot;
   dev_c6sup1_mpfpga_net_update_irq(d);
}

/* Clear a Network IRQ for the specified slot/port */
void dev_c6sup1_mpfpga_net_clear_irq(struct c6sup1_mpfpga_data *d,
                                     u_int slot,u_int port)
{
#if DEBUG_NET_IRQ
   vm_log(d->router->vm,"MP_FPGA","clearing NetIRQ for slot %u port %u\n",
          slot,port);
#endif
   d->irq_status &= ~(1 << slot);
   dev_c6sup1_mpfpga_net_update_irq(d);
}

/*
 * dev_c6sup1_access()
 */
void *dev_c6sup1_mpfpga_access(cpu_gen_t *cpu,struct vdevice *dev,
                               m_uint32_t offset,u_int op_size,u_int op_type,
                               m_uint64_t *data)
{
   struct c6sup1_mpfpga_data *d = dev->priv_data;
   struct nmc93cX6_group *grp;
   u_int i,slot,func;

   if (op_type == MTS_READ)
      *data = 0xFFFFFFFF;

#if DEBUG_ACCESS
   if (op_type == MTS_READ) {
      cpu_log(cpu,"MP_FPGA",
              "reading reg 0x%x at pc=0x%llx, ra=0x%llx (size=%u)\n",
              offset,cpu_get_pc(cpu),CPU_MIPS64(cpu)->gpr[MIPS_GPR_RA],
              op_size);
   } else {
      cpu_log(cpu,"MP_FPGA",
              "writing reg 0x%x at pc=0x%llx, ra=0x%llx "
              "data=0x%llx (size=%u)\n",
              offset,cpu_get_pc(cpu),CPU_MIPS64(cpu)->gpr[MIPS_GPR_RA],
              *data,op_size);
   }
#endif

   switch(offset) {
      case 0x0c:
      case 0x14:
      case 0x1c:
         if (op_type == MTS_READ)
            *data = 0;
         break;

      case 0x18:
         if (op_type == MTS_READ)
            *data = 0x8000;
         break;

      /* 0x3E80 is written regularly here (watchdog ?) */
      case 0x20004:
         break;

      /* Backplane EEPROMs */
      case 0x000020:
         if (op_type == MTS_WRITE) {
            //m_log("EEPROM","write access(BP): data=0x%4.4llx\n",*data);
            nmc93cX6_write(&d->router->bp_eeprom_group,(u_int)(*data));
         }
         break;

      /* Supervisor EEPROMs */
      case 0x000024:
         if (op_type == MTS_WRITE) {
            //m_log("EEPROM","write access(SUP): data=0x%4.4llx\n",*data);
            nmc93cX6_write(&d->router->sup_eeprom_group,(u_int)(*data));
         }
         break;

      /* Backplane/Supervisor EEPROMs read access */
      case 0x00003C:
         if (op_type == MTS_READ) {
            *data = 0x0000;

            /* Backplane EEPROMs */
            grp = &d->router->bp_eeprom_group;

            for(i=0;i<grp->nr_eeprom;i++) {
               if (nmc93cX6_is_active(grp,i))
                  *data |= nmc93cX6_get_dout(grp,i);
            }

            /* Supervisor EEPROMs */
            grp = &d->router->sup_eeprom_group;

            for(i=0;i<grp->nr_eeprom;i++) {
               if (nmc93cX6_is_active(grp,i))
                  if (nmc93cX6_get_dout(grp,i))
                     *data |= 0xFFFF; //nmc93cX6_get_dout(grp,i);
            }
         }
         break;

      /* Slot selection */
      case 0x000044:
         if (op_type == MTS_WRITE) {
            d->slot_sel = *data;
            slot = (d->slot_sel & 0xF000) >> 12;
            func = (d->slot_sel & 0x0F00) >> 8;

            if (slot <= C6SUP1_MAX_SLOTS) {
               grp = &d->router->slot_eeprom_group;
               grp->eeprom[0] = &d->router->slot_eeprom[slot-1];

               /* mark the slot as powered on */
               if (func == 0x02) {
                  //printf("Marking slot %u as powered ON\n",slot);
                  d->slot_status[slot-1] = TRUE;
               }
            }              
         }
         break;
         
      /* Slot EEPROM write */
      case 0x000048:
         if (op_type == MTS_WRITE)
            nmc93cX6_write(&d->router->slot_eeprom_group,(u_int)(*data));
         break;

      /* Slot EEPROM read */ 
      case 0x00004c:
         if (op_type == MTS_READ) {
            grp = &d->router->slot_eeprom_group;
            slot = (d->slot_sel & 0xF000) >> 12;
            func = (d->slot_sel & 0x0F00) >> 8;
            *data = 0;
            
            switch(func) {
               /* Presence + power ? */
               case 0x00:
                  *data = SLOT_NOT_PRESENT;

                  if (grp->eeprom[0] && grp->eeprom[0]->data) {
                     *data = 0;

                     /* The SUP slot is always powered */
                     if (d->slot_status[slot-1] || 
                         (slot == d->router->sup_slot))
                        *data |= SLOT_POWER_OK;
                  }
                  break;

               case 0x01:
                  *data = 0x0001;

                  if (grp->eeprom[0] && grp->eeprom[0]->data) {
                     *data = 0x0000;
                  }
                  break;

               /* Power-related */
               case 0x02:
                  *data = SLOT_POWER_CONVERTOR;
                  break;

               /* EEPROM reading */
               case 0x05:
                  if (nmc93cX6_is_active(grp,0))
                     *data |= nmc93cX6_get_dout(grp,0);
                  break;

               default:
                  cpu_log(cpu,"MP_FPGA","slot control: unknown func 0x%2.2x\n",
                          func);
            }
         }
         break;

      /* Slot Identification */
      case 0x000004:
         if (op_type == MTS_READ)
            *data = (d->router->sup_slot << 8) | 0x80;
         break;

      /* Unknown: EARL interrupt ? */
      /* 00:00:27: %CPU_MONITOR-3-PEER_EXCEPTION:
         CPU_MONITOR peer has failed due to exception , resetting [0/1] */
      case 0x000050:
         if (op_type == MTS_READ)
            *data = 0; //0xFFFF;
         break;

      case 0x000074:
         if (op_type == MTS_READ)
            *data = 0x0000; //0x3FFF;
         break;

#if DEBUG_UNKNOWN
      default:
         if (op_type == MTS_READ) {
            cpu_log(cpu,"MP_FPGA",
                    "read from unknown addr 0x%x, pc=0x%llx (size=%u)\n",
                    offset,cpu_get_pc(cpu),op_size);
         } else {
            cpu_log(cpu,"MP_FPGA",
                    "write to unknown addr 0x%x, value=0x%llx, pc=0x%llx "
                    "(op_size=%u)\n",offset,*data,cpu_get_pc(cpu),op_size);
         }
#endif
   }
	
   return NULL;
}

/* Shutdown the MP FPGA device */
static void 
dev_c6sup1_mpfpga_shutdown(vm_instance_t *vm,struct c6sup1_mpfpga_data *d)
{
   if (d != NULL) {
      /* Remove the device */
      dev_remove(vm,&d->dev);
      
      /* Free the structure itself */
      free(d);
   }
}

/* Initialize EEPROM groups */
void c6sup1_init_eeprom_groups(c6sup1_t *router)
{
   struct nmc93cX6_group *grp;
   struct cisco_eeprom *buf;

   router->bp_eeprom_group = eeprom_bp_group;
   router->sup_eeprom_group = eeprom_sup_group;
   router->slot_eeprom_group = eeprom_slot_group;

   /* XXX */
   buf = &router->bp_eeprom[0];
   grp = &router->bp_eeprom_group;
   grp->eeprom[0] = buf;  cisco_eeprom_copy(buf++, cisco_eeprom_find_c6k("C6K-CHASSIS-6509"));
   grp->eeprom[2] = buf;  cisco_eeprom_copy(buf++, cisco_eeprom_find_c6k("C6K-POWER-1000W"));
   grp->eeprom[3] = buf;  cisco_eeprom_copy(buf++, cisco_eeprom_find_c6k("C6K-POWER-1000W"));
   grp->eeprom[6] = buf;  cisco_eeprom_copy(buf++, cisco_eeprom_find_c6k("C6K-VTT"));
   grp->eeprom[7] = buf;  cisco_eeprom_copy(buf++, cisco_eeprom_find_c6k("C6K-VTT"));
   grp->eeprom[8] = buf;  cisco_eeprom_copy(buf++, cisco_eeprom_find_c6k("C6K-VTT"));

   buf = &router->sup_eeprom[0];
   grp = &router->sup_eeprom_group;
   grp->eeprom[0] = buf;  cisco_eeprom_copy(buf++, cisco_eeprom_find_c6k("C6K-SUP-SUP1A-2GE"));
   grp->eeprom[1] = buf;  cisco_eeprom_copy(buf++, cisco_eeprom_find_c6k("C6K-EARL-PFC1"));

   cisco_eeprom_copy(&router->slot_eeprom[0],
                     cisco_eeprom_find_c6k("C6K-SUP-SUP1A-2GE"));
   
   cisco_eeprom_copy(&router->slot_eeprom[8],
                     cisco_eeprom_find_c6k("C6K-LC-WS-X6248"));
}

/* 
 * dev_c6sup1_mpfpga_init()
 */
int dev_c6sup1_mpfpga_init(c6sup1_t *router,m_uint64_t paddr,m_uint32_t len)
{   
   struct c6sup1_mpfpga_data *d;

   /* Allocate private data structure */
   if (!(d = malloc(sizeof(*d)))) {
      fprintf(stderr,"MP_FPGA: out of memory\n");
      return(-1);
   }

   memset(d,0,sizeof(*d));
   d->router = router;
   
   vm_object_init(&d->vm_obj);
   d->vm_obj.name = "mp_fpga";
   d->vm_obj.data = d;
   d->vm_obj.shutdown = (vm_shutdown_t)dev_c6sup1_mpfpga_shutdown;

   /* Set device properties */
   dev_init(&d->dev);
   d->dev.name      = "mp_fpga";
   d->dev.phys_addr = paddr;
   d->dev.phys_len  = len;
   d->dev.handler   = dev_c6sup1_mpfpga_access;
   d->dev.priv_data = d;

   /* Map this device to the VM */
   vm_bind_device(router->vm,&d->dev);
   vm_object_add(router->vm,&d->vm_obj);
   return(0);
}
