/*	$NetBSD: vcimultinc.c,v 1.29 2012/10/04 13:15:00 leroy Exp $	*/

/*
 * Copyright (c) 2012 Sylvain Leroy.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Marc Horowitz.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: vcimultinc.c,v 1.29 2012/10/04 13:15:00 leroy Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/extent.h>
/* #include <sys/malloc.h> */
#include <sys/kmem.h>

#include <machine/autoconf.h>
#include <machine/bus.h>
#include <machine/vcimultinic.h>

#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_types.h>
#include <net/if_media.h>
#include <net/if_ether.h>

#include "vcimultinic_chan.h"

// Forward declaration
static int	vcimultinic_chan_match(struct device *, struct cfdata *, void *);
static void	vcimultinic_chan_attach(struct device *, struct device *, void *);
static int	vcimultinic_chan_ifinit(struct ifnet *ifp); // linked to the reset field of the ifnet struct

//
// Softc structure
//

/* static struct vcimultinic_chan_softc *main_vcimultinic_chan_softc = NULL; */

struct	vcimultinic_softc
{
  device_t		sc_dev;			/* generic device info */
  /* device-specific state */
  device_t		*sc_vif[1];		/* Virtual Interfaces handling */
  struct evcnt		sc_intr_count;		/* Interruption counter (for statistics) */
  struct evcnt		sc_rx_count;		/* Received data counter (in byte) */
  struct evcnt		sc_tx_count;		/* Sent data counter (in byte) */
  struct callout	sc_tick_ch;		/* tick callout */
  struct callout	sc_restart_ch;		/* restart callout */
  paddr_t		sc_base_addr;		/* physical base addr of the controller */
  vaddr_t		sc_reg_size;		/* size to map */
  bus_space_tag_t	sc_iot;			/* bus space IO tag */
  bus_space_handle_t	sc_ioh;			/* bus_space IO handle */
  bus_dma_tag_t		sc_dmat;		/* bus DMA tag */
  bus_dmamap_t		sc_dmam;		/* bus DMA map */
  irq_t			sc_ih;			/* IRQ handler */
  /* vaddr_t		sc_bufidx;		/\* current position in buffer *\/ */
  /* int			sc_curchild;		/\* child whose bufq is in work *\/ */
  /* int			sc_bytesleft;		/\* bytes left to transfer *\/ */
  /* u_int8_t		type;			/\* controller type, 1 or 2 *\/ */
  /*
  ** ADD HERE THE OTHER VALUES YOU NEED FOR AN ETHERNET INTERFACE
   */
  struct ethercom	ns_ec;			/* Ethernet common part */
  uint8_t		enaddr[ETHER_ADDR_LEN]; /* Ethernet MAC addr */
  struct rx_buf		*rx_buf;		/* Buffer management for rx */
  struct tx_buf		*tx_buf;		/* Buffer management for tx */
  /* struct ifnet		*ifp;			/\* because interface is in : ns_ec->ec_if *\/ */
  /* uint32_t		nb_channel; */
};

CFATTACH_DECL_NEW(vcimultinic_chan,
		  sizeof(struct vcimultinic_softc),
		  vcimultinic_chan_match,
		  vcimultinic_chan_attach,
		  NULL,
		  NULL);

// That include needs the "sc" structure to be known
#include "vcimultinic_chan_helper.h"
#include "vcimultinic_common.h"

/*
** \brief Check if we handle a device
**
** \param
** \param
** \param Information from the parent device driver
**
**/
static int
vcimultinic_chan_match(struct device *parent,
		   struct cfdata *match,
		   void *aux)
{
  // Because we are on a soclib bus, we get a tsar device structure
  // (we do that because "aux" is void*)
  struct tsardevs_attach_args *tsd = aux;

/* #ifdef VCIMULTINIC_DEBUG */
/*   aprint_normal(": SocLib VCI Multi NIC driver (match function)\n"); */
/* #endif */

  if (strcmp(tsd->tsd_devtype, "soclib:vcimultinic_chan") != 0)
    return 0; // our driver doesn't handle that device
  return 1; // we handle that device
}

/*
** \brief Initialize the device
**
** \param
** \param
** \param Information from the parent device driver
**
**/
static void
vcimultinic_chan_attach(struct device *parent,
		   struct device *self,
		   void *aux)
{
  struct vcimultinic_softc	*sc = device_private(self);
  struct tsardevs_attach_args	*tsd = aux;
  /* paddr_t* rx_data_0 = (paddr_t*) ???; */
  /* paddr_t* tx_data_0 = (paddr_t*) ???; */
  uint32_t			i = 0;


  aprint_normal(": SocLib VCI Channel Multi NIC driver\n");

  // We get the values for the currently used network interface
  // from the device tree structure filled in tsd structure
  sc->sc_dev = self;
  sc->sc_iot = tsd->tsd_tag;
  sc->sc_dmat = tsd->tsd_dmatag;
  sc->sc_base_addr = tsd->tsd_reg[0].reg_addr; // physical base addr
  sc->sc_reg_size = tsd->tsd_reg[0].reg_size;

  // We init some values
  sc->rx_buf = NULL;
  sc->tx_buf = NULL;

  // We init callout structures
  // use CALLOUT_MPSAFE instead of 0 as flag if the function called is MP safe
  callout_init(&sc->sc_tick_ch, 0);
  callout_init(&sc->sc_restart_ch, 0);

  /*****************************************/
  /* Initialize and attach event counters. */
  /*****************************************/
  /* see : http://www.daemon-systems.org/man/evcnt.9.html */
  evcnt_attach_dynamic(&sc->sc_intr_count,	/* ev */
		       EVCNT_TYPE_INTR,		/* type */
		       NULL,			/* parent */
		       device_xname(self),	/* group */
		       "intr");			/* name */
  evcnt_attach_dynamic(&sc->sc_rx_count, EVCNT_TYPE_INTR,
		       &sc->sc_intr_count, device_xname(self), "intr rx");
  evcnt_attach_dynamic(&sc->sc_tx_count, EVCNT_TYPE_INTR,
		       &sc->sc_intr_count, device_xname(self), "intr tx");

  /**********************/
  /* Map nic i/o space. */
  /**********************/
  if (bus_space_map(sc->sc_iot,			/* bus space tag */
  		    sc->sc_base_addr,		/* Nic physical base addr (from DTS file) */
  		    sc->sc_reg_size,		/* Mapping the whole controller (from DTS file)*/
  		    0,				/* flags */
  		    &(sc->sc_ioh)))		/* bus space handle */
    {
      aprint_error_dev(self, ": couldn't map nic i/o space\n");
      return;
    }
  aprint_normal_dev(self, "mapped at 0x%x\n", sc->sc_ioh);

  /***************/
  /* IRQ handler */
  /***************/
  char buf[40];

  sc->sc_ih = intr_establish( tsd->tsd_irq,		/* IRQ path */
			     IPL_BIO,			/* ipl */
			     device_xname(self),	/* name */
			     buf,			/* intr string */
			     vcimultinic_intr,		/* Handler function */
			     sc,			/* ih_arg */
			     NULL);			/* cpu_info struct */

  if (sc->sc_ih == NULL)
    {
      aprint_error_dev(self, "can't establish interrupt\n");
      return;
    }
  else
    {
      aprint_normal_dev(self, "interrupting at %s\n", buf);
    }

  /*****************************************************/

  /**********************/
  /* Map DMA i/o space. */
  /**********************/
  /* if (bus_space_map(sc->sc_dmat, */
  /* 		    tsd->ia_iomem[0].ir_addr, */
  /* 		    tsd->ia_iomem[0].ir_size, */
  /* 		    0, */
  /* 		    &(tsd->sc_dmah))) { */
  /*   printf(": can't map DMA i/o space\n"); */
  /*   return; */
  /* } */
  /* aprint_normal_dev(self, "DMA mapped at 0x%x\n", sc->sc_ioh); */

  /* error = bus_dmamap_create(sc->sc_dmat, MAXPHYS, 1, MAXPHYS, */
  /* 			    0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &sc->sc_dmamap); */
  /* if (error) { */
  /*   aprint_error_dev(self, "can't create DMA map (%d)\n", error); */
  /*   return; */
  /* } */

  /* aprint_normal_dev(self, ": init OK\n"); */

  /***************************************/
  /* Get information from the controller */
  /***************************************/

  // 0x20000 = base_addr + 8 channels size

  /* for (i = 0; i < ETHER_ADDR_LEN; i++) */
  /*   { */
  /*     /\* sc->enaddr[i] = bus_space_read_1(sc->sc_iot, sc->sc_ioh, 0x20000 + GENERAL_MAC_4); *\/ */
  /*     /\* sc->enaddr[i] = 0xCA; *\/ */
  /*   } */

  uint32_t mac4 = 0;
  uint32_t mac2 = 0;
  /* uint32_t reg_page_size = 0x1000; */
  /* uint32_t channel_size = 0x4000; */
  /* uint32_t mac4_addr_offset = (reg_page_size * sel_channel) + (MAC_4 * sizeof(uint32_t)); */
  /* uint32_t mac2_addr_offset = (reg_page_size * sel_channel) + (MAC_2 * sizeof(uint32_t)); */

  /* aprint_normal_dev(self, "MAC4 : space : %x, handle : %x , offset : %x\n", sc->sc_iot, sc->sc_ioh, mac4_addr_offset); */
  /* aprint_normal_dev(self, "MAC2 : space : %x, handle : %x , offset : %x\n", sc->sc_iot, sc->sc_ioh, mac2_addr_offset); */

  /* We want to read/write our MAC addr */
  mac4 = VMN_CHAN_READ_STATUS_REG(sc, NIC_MAC_4);
#ifdef VCIMULTINIC_DEBUG
  aprint_normal_dev(self, "*** mac4 is :%x\n", mac4);
#endif
  sc->enaddr[0] = (mac4 & 0xFF000000) >> 24;
  sc->enaddr[1] = (mac4 & 0x00FF0000) >> 16;
  sc->enaddr[2] = (mac4 & 0x0000FF00) >> 8;
  sc->enaddr[3] = (mac4 & 0x000000FF);

  mac2 = VMN_CHAN_READ_CONF_REG(sc, NIC_MAC_2);
#ifdef VCIMULTINIC_DEBUG
  aprint_normal_dev(self, "*** mac2 is :%x\n", mac2);
#endif
  sc->enaddr[4] = (mac2 & 0x0000FF00) >> 8;
  sc->enaddr[5] = (mac2 & 0x000000FF);

  /* We print the MAC ADDR discovered from the controller */
  aprint_normal_dev(self, "master MAC addr is ");
  for (i = 0; i < ETHER_ADDR_LEN; i++)
    {
      aprint_normal("%02x", sc->enaddr[i] & 0xff);
      if (i != ETHER_ADDR_LEN-1)
  	aprint_normal(":");
      else
  	aprint_normal("\n");
    }

  /* MIGHT BE REMOVED */
  aprint_normal_dev(self, "Master Ethernet address writen is %s\n",
  		    ether_sprintf(sc->enaddr));

  /*********************************************************/
  /* Create the EtherCom interface and attach the interface*/
  /*********************************************************/

  struct ifnet	* const ifp = &sc->ns_ec.ec_if;

  strlcpy(ifp->if_xname, device_xname(self), IFNAMSIZ);
  // Done to change the network interface name.
  /* strlcpy(ifp->if_xname, "vmn", IFNAMSIZ); Getting a problem with the number : vmn0,1,2,3,... */
  /* aprint_normal("*** devname : %s\n", device_xname(self)); */
  ifp->if_softc = sc;						/* soft structure for other functions */
  ifp->if_flags = IFF_BROADCAST|IFF_SIMPLEX|IFF_NOTRAILERS;
  /* ifp->if_output = ; */
  /* ifp->if_input = ; */
  ifp->if_start = vcimultinic_ifstart;				/* output routine (packet sender)*/
  ifp->if_ioctl = vcimultinic_ifioctl;				/* ioctl routine */
  ifp->if_init = vcimultinic_chan_ifinit;				/* initialize (ifconfig up) */
  ifp->if_stop = vcimultinic_ifstop;				/* stop routine (ifconfig down) */
  ifp->if_watchdog = vcimultinic_ifwatchdog;			/* timer handling NOT USED AT THE MOMENT*/
  /* ifp->if_drain = ;						/\* routine to release resources *\/ */
  ifp->if_timer = 0;						/* time 'til if_watchdog called */

  IFQ_SET_READY(&ifp->if_snd);

  // Attach the interface
  if_attach(ifp);
  ether_ifattach(&(sc)->ns_ec.ec_if, (sc)->enaddr);

  return;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
/*
**	initialize the interface (ifconfig up)
**/
static int
vcimultinic_chan_ifinit(struct ifnet *ifp) // linked to the reset field of the ifnet struct ???
{
  struct vcimultinic_softc	*sc = ifp->if_softc;
  int				s = 0;
  int				i = 0;
  /* uint32_t reg; */

#ifdef VCIMULTINIC_DEBUG
  aprint_normal("vcimultinic: Entering %s\n", __func__);
#endif

  /* s = splnet(); */

  // That function reset any init values
  /* vcimultinic_ifinit(ifp); */

  // If we enter here but interface has not been set to UP, we leave
  if ((ifp->if_flags & IFF_UP) == 0)
    {
      /* splx(s); */
      return 0;
    }

#ifdef VCIMULTINIC_DEBUG
  aprint_normal("vcimultinic: [%s] Allocating rx/tx bufs\n", __func__);
#endif

  /* Pre-allocate receivers mbuf, make the bufs */
  sc->rx_buf = kmem_alloc( sizeof(struct rx_buf) * VMN_RX_SW_NBUF, KM_SLEEP);
  sc->tx_buf = kmem_alloc( sizeof(struct tx_buf) * VMN_TX_SW_NBUF, KM_SLEEP);
  if (sc->rx_buf == NULL || sc->tx_buf == NULL)
    {
      // We free the buffer if one couldn't be allocated
      if (sc->rx_buf != NULL)
	kmem_free(sc->rx_buf, sizeof(struct rx_buf) * VMN_RX_SW_NBUF);
      if (sc->tx_buf != NULL)
	kmem_free(sc->tx_buf, sizeof(struct tx_buf) * VMN_TX_SW_NBUF);

      // Error case
      aprint_error("%s: out of memory for bufs\n", device_xname(sc->sc_dev));
      return ENOMEM;
    }

  // initialize values for our internal buffer management structures
  sc->rx_buf->current_sw_buf = 0;
  sc->rx_buf->current_hw_buf = 0;
  sc->tx_buf->current_sw_buf = 0;
  sc->tx_buf->current_hw_buf = 0;
  sc->rx_buf->pkt_ptr = 4;
  sc->tx_buf->pkt_ptr = 4;
  sc->rx_buf->rptr = 34 * 4; // buffers start after 34 words of 32 bits
  sc->tx_buf->wptr = 34 * 4;
  for (i = 0; i < VMN_NB_CONTAINER; i++)
    {
      memset(sc->rx_buf->container[i], '\0', VMN_CONTAINER_SIZE);
      memset(sc->tx_buf->container[i], '\0', VMN_CONTAINER_SIZE);
    }

  /* #ifdef VCIMULTINIC_DEBUG */
  /*   aprint_normal("vcimultinic: [%s] Linking chains\n", __func__); */
  /* #endif */

  // Initialize the lists
  /* for (i = 1; i < VMN_RX_NBUF; i++) */
  /*     sc->rx_list[i-1].next = &(sc->rx_list[i]); */
  /* for (i = 1; i < VMN_TX_NBUF; i++) */
  /*     sc->tx_list[i-1].next = &(sc->tx_list[i]); */
  // mark the end of each list
  /* sc->rx_list[VMN_RX_NBUF-1].next = NULL; */
  /* sc->tx_list[VMN_TX_NBUF-1].next = NULL; */

  /* // HV SPECIFIC */
  /* // Configure the controller... */
  /* vmn_hv_bc_on(sc); // activate broadcast */
  /* vmn_hv_set_vis(sc, 0xffffffff); // activate channels */
  /* vmn_hv_set_mac_addr_set(sc, 0xffffffff); // activate channels by telling the hardware that the mac addr is set for these channels */
  /* vmn_hv_tdm_on_and_set_timer(sc, 0x1000); // activate TDM and set its timer */
  /* /\* vmn_hv_tdm_on_and_set_timer(sc, 1, 0x2000); // activate TDM and set its timer *\/ */
  /* /\* vmn_hv_tdm_on_and_set_timer(sc, 2, 0x3000); // activate TDM and set its timer *\/ */
  /* /\* vmn_hv_tdm_on_and_set_timer(sc, 3, 0x4000); // activate TDM and set its timer *\/ */
  /* // .. and then activate it */
  /* vmn_hv_nic_on(sc); */

  /* // Let see if everything is OK */
  /* vmn_print_hv_reg(sc); */

  /* start ticks calls */
  callout_reset(&sc->sc_tick_ch, MY_HZ, vcimultinic_ifpoll, sc);

#ifdef VCIMULTINIC_DEBUG
  aprint_normal("vcimultinic: [%s] Setting interface to RUNNING\n", __func__);
#endif

  // get a lock on the controller
  s = splnet();

  // Activate the controller
  ifp->if_flags |= IFF_RUNNING;

  // release the lock on the controller
  splx(s);

  VMN_CHAN_WRITE_REG(sc, NIC_RX_RUN, 0x1);
  VMN_CHAN_WRITE_REG(sc, NIC_TX_RUN, 0x1);

#ifdef VCIMULTINIC_DEBUG
  aprint_normal("vcimultinic: [%s] exit success\n", __func__);
#endif

  return 0;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
