/*------------------------------------------------------------------------------------------*\
 *   
 *   Copyright (C) 2006 AVM GmbH <fritzbox_info@avm.de>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation version 2 of the License.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
\*------------------------------------------------------------------------------------------*/
#include <stdarg.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <net/pkt_sched.h>

#if !defined(CONFIG_NETCHIP_ADM69961)
#define CONFIG_NETCHIP_ADM69961
#endif
#include <linux/avm_event.h>
#include <linux/avm_cpmac.h>
#include <linux/avm_led.h>

#include "cpmac_if.h"
#include "cpmac_const.h"
#include "cpmac_types.h"
#include "cpmac_debug.h"
#include "cpmac_main.h"
#include "cpphy_const.h"
#include "cpphy_types.h" /* for cpphy_global_t */

#include <linux/if_vlan.h>

#ifdef CONFIG_IP_MULTICAST_FASTFORWARD
struct mcfw_netdriver *cpmac_mcfw_netdrv = 0;
int cpmac_mcfw_sourceid = 0;
#endif

void *cpmac_event_handle;
unsigned int cpmac_devices_installed;
unsigned int timer_count;
struct timer_list timer;
dev_desc_t g_dev_array[CPMAC_MAX_PHY];

MODULE_AUTHOR("Maintainer: AVM GmbH");
MODULE_DESCRIPTION("Ethernet driver for FRITZ!Box");
MODULE_LICENSE("\n(C) Copyright 2005, AVM GmbH\n");

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int cpmac_main_find_phy (unsigned int need_switch)
{
  unsigned int i;
  cpmac_err_t status = need_switch ? CPMAC_ERR_NOERR : CPMAC_ERR_NO_SWITCH;

  for (i=0; i<CPMAC_MAX_PHY; i++) {
    if ((g_dev_array[i].p_dev) && (g_dev_array[i].service_funcs.control_req (g_dev_array[i].phy_handle, 
        CPMAC_CONTROL_REQ_IS_SWITCH) == status)) {
      break;
    }
  }
  return i;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void cpmac_main_handle_mdio_status_ind (cpmac_priv_t *cpmac_priv,
    unsigned int linked)
{
  struct net_device *p_dev = cpmac_priv->owner;

  if (linked) {
    if (!netif_carrier_ok (p_dev)) {
      netif_carrier_on (cpmac_priv->owner);
      DEB_INFO("cpmac_main_handle_mdio_status_ind, carrier on for %s\n",
          p_dev->name);
    }
    if (netif_running (p_dev) && netif_queue_stopped (p_dev)) {
      netif_wake_queue (p_dev);
    }
  } else {
    if (netif_carrier_ok (p_dev)) {
      netif_carrier_off (p_dev);
      DEB_INFO("cpmac_main_handle_mdio_status_ind, carrier off for %s\n", 
          p_dev->name);
    }
    if (!netif_queue_stopped(p_dev)) {
      netif_stop_queue(p_dev);     /* dont let kernel send packets anymore */
    }
  }
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static irqreturn_t cpmac_main_isr (int irq, void *p_param, struct pt_regs *regs)
{
  cpmac_priv_t *cpmac_priv = (cpmac_priv_t *)p_param;

  if (cpmac_priv->set_to_close) {
    DEB_INFO("cpmac_main_isr, set to close\n");
    /* to check: is this necessary */
    g_dev_array[cpmac_priv->inst].service_funcs.isr_end (
      g_dev_array[cpmac_priv->inst].phy_handle);
  } else {
    /* performance-impact */
    /*--- disable_irq_nosync(cpmac_priv->intr); ---*/
#   if defined(CPU_PROFILE_LOG)
    ohio_simple_profiling_text("cpmac_main_isr");
#   endif
    tasklet_schedule(&cpmac_priv->tasklet);
  }
  return IRQ_HANDLED;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void cpmac_main_start_timer (void)
{
  DEB_TRC("cpmac_main_start_timer, init\n");

  if (!timer_count++) {
    timer.expires = jiffies + HZ / CPMAC_TICK_PER_SEC;
    add_timer (&timer);
  } else {
    DEB_INFO("cpmac_main_start_timer, now %u timer\n",
        timer_count);
  }
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void cpmac_main_stop_timer (void)
{
  DEB_TRC("cpmac_main_stop_timer, init\n");

  if (timer_count) {
    timer_count--;
    if (!timer_count) {
      del_timer_sync (&timer);
    } else {
      DEB_INFO("cpmac_main_stop_timer, still %u timer\n",
          timer_count);
    }
  }
}

/*------------------------------------------------------------------------------------------*\
  design: one tick handler for all cpphy driver 
\*------------------------------------------------------------------------------------------*/
static void cpmac_main_tick_timer_expiry(unsigned long p_cb_param)
{
  unsigned int i;

  /*--- DEB_TRC("cpmac_main_tick_timer_expiry, init\n"); ---*/

  for (i=0; i<CPMAC_MAX_PHY; i++) {
    if (g_dev_array[i].p_dev) {
      cpmac_priv_t *cpmac_priv = (cpmac_priv_t *)netdev_priv (g_dev_array[i].p_dev);

      if (!test_bit(0, &cpmac_priv->set_to_close)) {
        cpmac_priv->DoTick = 1;
#       if defined(CPU_PROFILE_LOG)
        ohio_simple_profiling_text("cpmac_main_tick_timer_expiry");
#       endif
        tasklet_schedule(&cpmac_priv->tasklet);
      }
    }
  }
  if (timer_count) {
    timer.expires = jiffies + HZ / CPMAC_TICK_PER_SEC;
    add_timer (&timer);
  }
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
int cpmac_main_timer_init (void)
{
  /*--- unsigned int clocks_per_tick = cpmac_cpu_freq/HZ; ---*/

  DEB_TRC("cpmac_main_timer_init, init\n");

  init_timer (&timer);
  timer.expires = 0;
  timer.data = 0;
  timer.function = cpmac_main_tick_timer_expiry;
  return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static unsigned int cpmac_main_setup_configuration (cpmac_capabilities_t *capabilities)
{
  unsigned int net_flags = 0;

  capabilities->promiscous             = CPMAC_CFG_PROMISCOUS;
  capabilities->broadcast              = CPMAC_CFG_BROADCAST;
  capabilities->multicast              = CPMAC_CFG_MULTICAST;
  capabilities->short_frames           = CPMAC_CFG_SHORT_FRAMES;
  capabilities->long_frames            = CPMAC_CFG_LONG_FRAMES;
  capabilities->auto_negotiation       = CPMAC_CFG_AUTO_NEGOTIATION;    /* ??? */
  if (capabilities->broadcast) {
    net_flags |= IFF_BROADCAST;
  }
  if (capabilities->multicast) {
    net_flags |= IFF_MULTICAST;
  }
  return net_flags;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void cpmac_led_remove(int handle)
{
   /* FIXME Is it really necessary to do anything? */
}


void cpphy_mdio_event_dataupdate (cpphy_mdio_t *mdio); /* FIXME */
/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int cpmac_main_dev_open(struct net_device *p_dev)
{
  cpmac_priv_t *cpmac_priv = (cpmac_priv_t *)netdev_priv (p_dev);
  cpmac_err_t ret;

  DEB_INFO("cpmac_main_dev_open, %s\n", p_dev->name);

  if (request_irq (cpmac_priv->intr, cpmac_main_isr, SA_INTERRUPT, 
      "Cpmac Driver", cpmac_priv)) {
    DEB_ERR("cpmac_main_dev_open, failed to register the irq %u for %s\n",
        cpmac_priv->intr, p_dev->name); 
    return (-1);          
  }

  cpmac_priv->led_handle = avm_led_alloc_handle("cpmac", 
                                                cpmac_priv->inst,
                                                cpmac_led_remove);
  if (cpmac_priv->led_handle == 0) {
    DEB_ERR("[avm cpmac] Error! Could not allocate LED handle!\n");
  } else {
    cpphy_mdio_event_dataupdate (&((cpphy_global_t *) g_dev_array[cpmac_priv->inst].phy_handle)->mdio);
  }

  /* start dma */
  if ((ret = g_dev_array[cpmac_priv->inst].service_funcs.control_req (g_dev_array[cpmac_priv->inst].phy_handle,
      CPMAC_CONTROL_REQ_START_DMA)) != CPMAC_ERR_NOERR) {
    DEB_ERR("cpmac_main_dev_open, failed to start dma, %u\n",
        ret); 
    free_irq(cpmac_priv->intr, cpmac_priv);
    return (-1);          
  }

  netif_start_queue(p_dev);

  return(0);
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int cpmac_main_dev_close(struct net_device *p_dev)
{
  cpmac_priv_t *cpmac_priv = (cpmac_priv_t *)netdev_priv (p_dev);

  DEB_INFO("cpmac_main_dev_close, init, %s\n", p_dev->name);

  /* inform the upper layers. */
  if (!netif_queue_stopped(p_dev)) {
    netif_stop_queue(p_dev);
  }

  /* full teardown tx/rx before free int */
  g_dev_array[cpmac_priv->inst].service_funcs.control_req (g_dev_array[cpmac_priv->inst].phy_handle,
      CPMAC_CONTROL_REQ_TEARDOWN, CPPHY_RX_TEARDOWN | CPPHY_TX_TEARDOWN | CPPHY_FULL_TEARDOWN | CPPHY_BLOCKING_TEARDOWN);

  free_irq(cpmac_priv->intr, cpmac_priv);

  return(0);
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int cpmac_main_dev_send (struct sk_buff *skb, struct net_device *p_dev)
{
  cpmac_priv_t *cpmac_priv = (cpmac_priv_t *)netdev_priv (p_dev);
  unsigned int drop = 0;
  cpmac_err_t ret = CPMAC_ERR_NOERR;

  DEB_TRC("cpmac_main_dev_send, init, %u\n", skb->len);

  skb_trace(skb, 19);

  if (skb->len < CPMAC_VLAN_TCI_END_OFFSET) {
    /* drop frame shorter than 14 bytes */
    drop = 1;
    DEB_WARN("cpmac_main_dev_send, drop short pkt (%u)\n",
        skb->len);
  } else if (CPMAC_VLAN_IS_802_1Q_FRAME (skb->data)) {
    if (!cpmac_priv->enable_vlan) {
      /* sending a 802.1q frame, when not configured for 802.1q: drop it.*/
      drop = 1;
      DEB_WARN("cpmac_main_dev_send, drop vlan pkt (%u)\n",
          skb->len);
    /*--- } else { ---*/		
        /*--- unsigned short vid = CPMAC_VLAN_GET_VLAN_ID(skb->data); ---*/
        /*--- DEB_ERR("[avm cpmac] VLAN packet (ID = 0x%x) from system\n", vid); ---*/
    }
  } else if (cpmac_priv->enable_vlan) {
    /*--- DEB_ERR("[avm cpmac] Adding VLAN tag %u to packet\n", 17); ---*/
    /*--- skb = vlan_put_tag(skb, 17); ---*/
/*--- #   if 0 ---*/
    /* sending a non 802.1q frame, when configured for 802.1q: drop it.*/
    drop = 1;
    DEB_WARN("cpmac_main_dev_send, drop non vlan pkt (%u bytes)\n", skb->len);
/*--- #   endif ---*/ /*--- #if 0 ---*/
  }

  p_dev->trans_start = jiffies;

  if (!drop) {
    ret = g_dev_array[cpmac_priv->inst].service_funcs.data_to_phy (g_dev_array[cpmac_priv->inst].phy_handle, skb);
#   ifdef CONFIG_IP_MULTICAST_FASTFORWARD
    if (ret == CPMAC_ERR_NOERR && cpmac_mcfw_netdrv) {
       if (cpmac_priv->enable_vlan) {
	      /*
		   * VLAN is always there, else drop will be set
		   */
	      unsigned short vid = CPMAC_VLAN_GET_VLAN_ID(skb->data);
	      (void)mcfw_snoop_send(cpmac_mcfw_netdrv,
							    cpmac_priv->map_vid_portset[vid & 0xf], skb);
	   } else {
         /*
		  * HW 94.0.0.0
		  */
          mcfw_portset set;
          mcfw_portset_reset(&set);
          mcfw_portset_port_add(&set, 0);
	      (void)mcfw_snoop_send(cpmac_mcfw_netdrv, set, skb);
	   }
    }
#   endif
  }
  if(ret == CPMAC_ERR_NO_BUFFER) {
    /*--- DEB_INFO("[avm cpmac test] CPMAC_ERR_NO_BUFFER\n"); ---*/
	netif_stop_queue(p_dev);
    return NETDEV_TX_BUSY;
  }
  if (drop || (ret != CPMAC_ERR_NOERR)) {
    DEB_INFO("cpmac_main_dev_send, drop, %u, %u\n", 
        drop, ret);
    cpmac_if_data_to_phy_complete (cpmac_priv, skb, CPMAC_ERR_DROPPED);
# ifdef CPMAC_SIGNAL_CONGESTION
  } else {
    cpmac_priv->TxProbe = 1;
    cpmac_priv->TxCount++;
    if (cpmac_priv->TxCount - cpmac_priv->TxComplete == CPPHY_MAX_TX_BUFFERS) {
      netif_stop_queue (p_dev);
      cpmac_priv->TxProbe = 0;
      if (cpmac_priv->TxProbeComplete) {
        cpmac_priv->TxProbeComplete = 0;
        netif_wake_queue (p_dev);
      }
    } else {
      cpmac_priv->TxProbe = 0;
      cpmac_priv->TxProbeComplete = 0;
    }
# endif
  }
  return NETDEV_TX_OK;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#ifdef CPMAC_TX_TIMEOUT
static void cpmac_main_tx_timeout (struct net_device *p_dev)
{
  cpmac_priv_t *cpmac_priv = (cpmac_priv_t *)netdev_priv (p_dev);

  DEB_WARN("cpmac_main_tx_timeout, %s, latency: %lu\n", 
      p_dev->name, jiffies - p_dev->trans_start);
  g_dev_array[cpmac_priv->inst].service_funcs.control_req (g_dev_array[cpmac_priv->inst].phy_handle,
      CPMAC_CONTROL_REQ_TEARDOWN, CPPHY_TX_TEARDOWN | CPPHY_CALLBACK_TEARDOWN);
}
#endif

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static struct net_device_stats *cpmac_main_get_net_stats (struct net_device *p_dev)
{
  cpmac_priv_t *cpmac_priv = (cpmac_priv_t *)netdev_priv (p_dev);

  /* do not access the hardware if it is in the reset state. */
  if (!test_bit(0, &cpmac_priv->set_to_close)) {

    g_dev_array[cpmac_priv->inst].service_funcs.control_req (g_dev_array[cpmac_priv->inst].phy_handle, 
        CPMAC_CONTROL_REQ_HW_STATUS, &cpmac_priv->net_dev_stats);
    cpmac_priv->net_dev_stats.rx_errors += cpmac_priv->local_stats_rx_errors;
    cpmac_priv->net_dev_stats.rx_length_errors += cpmac_priv->local_stats_rx_length_errors;
    cpmac_priv->net_dev_stats.rx_packets = cpmac_priv->local_stats_rx_packets;
    cpmac_priv->net_dev_stats.rx_bytes = cpmac_priv->local_stats_rx_bytes;
    cpmac_priv->net_dev_stats.tx_errors += cpmac_priv->local_stats_tx_errors;
    cpmac_priv->net_dev_stats.tx_packets = cpmac_priv->local_stats_tx_packets;
    cpmac_priv->net_dev_stats.tx_bytes = cpmac_priv->local_stats_tx_bytes;
    cpmac_priv->net_dev_stats.tx_dropped = p_dev->qdisc->qstats.drops;
  }

  return &cpmac_priv->net_dev_stats;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void cpmac_main_multicast_set (struct net_device *p_dev)
{
  cpmac_priv_t *cpmac_priv = (cpmac_priv_t *)netdev_priv (p_dev);
  cpmac_capabilities_t *capabilities = &cpmac_priv->capabilities;

  if (p_dev->flags & IFF_PROMISC) {
    if (capabilities->promiscous) {       
      g_dev_array[cpmac_priv->inst].service_funcs.control_req (
        g_dev_array[cpmac_priv->inst].phy_handle, CPMAC_CONTROL_REQ_PROMISCOUS);
      DEB_INFO("cpmac_main_multicast_set, %s set to promiscous mode\n",
          p_dev->name);	
    } else {
      DEB_WARN("cpmac_main_multicast_set, %s not set to promiscous mode\n",
          p_dev->name);
    }
  } else if (p_dev->flags & IFF_ALLMULTI) {
    if (capabilities->multicast) {	
      g_dev_array[cpmac_priv->inst].service_funcs.control_req (
        g_dev_array[cpmac_priv->inst].phy_handle, CPMAC_CONTROL_REQ_MULTI_ALL);
      DEB_INFO("cpmac_main_multicast_set, %s has been set to the ALL_MULTI mode\n",
          p_dev->name);
    } else {
      DEB_WARN("cpmac_main_multicast_set, %s not configured for ALL MULTI mode\n",
          p_dev->name);
    }
  } else if (capabilities->multicast) {
    g_dev_array[cpmac_priv->inst].service_funcs.control_req (
      g_dev_array[cpmac_priv->inst].phy_handle, CPMAC_CONTROL_REQ_MULTI_SINGLE);
    DEB_INFO("cpmac_main_multicast_set, %s configured for %d multicast addresses\n", 
        p_dev->name, p_dev->mc_count);
  } else {
    DEB_WARN("cpmac_main_multicast_set, %s has not been configured for multicast handling\n",
        p_dev->name);
  }
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int cpmac_main_ioctl (struct net_device *p_dev, struct ifreq *ifr, int cmd) 
{
  cpmac_priv_t *cpmac_priv = (cpmac_priv_t *)netdev_priv (p_dev);
  int ret = 0;

  switch(cmd) {
    case AVM_CPMAC_IOCTL_INFO:
      {
        struct avm_switch_info *info = (struct avm_switch_info *) ifr->ifr_data;

        info->internal_ports = cpmac_devices_installed;
        if (g_dev_array[cpmac_priv->inst].p_dev) {
          info->external_ports = g_dev_array[cpmac_priv->inst].service_funcs.control_req (g_dev_array[cpmac_priv->inst].phy_handle, 
              CPMAC_CONTROL_REQ_PORT_COUNT);
        } else {
          DEB_WARN("cpmac_main_ioctl, info, dev %u not registered\n",
              cpmac_priv->inst);
          info->external_ports = 0;
        }
      }
      break;
    case AVM_CPMAC_IOCTL_SET_CONFIG_N:
      if (g_dev_array[cpmac_priv->inst].p_dev) {
        cpmac_err_t err;

        if ((err = g_dev_array[cpmac_priv->inst].service_funcs.control_req (
            g_dev_array[cpmac_priv->inst].phy_handle, 
            CPMAC_CONTROL_REQ_SET_CONFIG, ifr->ifr_data)) != CPMAC_ERR_NOERR) {
          DEB_WARN("cpmac_main_ioctl, config, dev %u, err: %u\n",
              cpmac_priv->inst, err);
          ret = (err == CPMAC_ERR_NO_VLAN_POSSIBLE) ? -EIO : -EINVAL;
        }
      } else {
        DEB_WARN("cpmac_main_ioctl, config, dev %u not registered\n",
            cpmac_priv->inst);
        ret = -ENXIO;
      }
      break;
    case AVM_CPMAC_IOCTL_GET_CONFIG_N:
      if (g_dev_array[cpmac_priv->inst].p_dev) {
        cpmac_err_t err;

        if ((err = g_dev_array[cpmac_priv->inst].service_funcs.control_req (
            g_dev_array[cpmac_priv->inst].phy_handle, 
            CPMAC_CONTROL_REQ_GET_CONFIG, ifr->ifr_data)) != CPMAC_ERR_NOERR) {
          DEB_WARN("cpmac_main_ioctl, get config, dev %u, err: %u\n",
              cpmac_priv->inst, err);
          ret = (err == CPMAC_ERR_NO_VLAN_POSSIBLE) ? -EIO : -EINVAL;
        }
      } else {
        DEB_WARN("cpmac_main_ioctl, get config, dev %u not registered\n",
            cpmac_priv->inst);
        ret = -ENXIO;
      }
      break;
    default:
      DEB_ERR("cpmac_main_ioctl, unknown ioctl %u\n", cmd);
      ret = -EINVAL;
      break;
  }

  return ret;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void cpmac_main_dev_init (struct net_device *p_dev)
{
  cpmac_priv_t *cpmac_priv = (cpmac_priv_t *)netdev_priv (p_dev);

  memset (cpmac_priv, 0, sizeof(cpmac_priv_t));
  ether_setup (p_dev); 
  p_dev->open = &cpmac_main_dev_open;
  p_dev->stop = &cpmac_main_dev_close;
  p_dev->hard_start_xmit = &cpmac_main_dev_send;
  p_dev->get_stats = &cpmac_main_get_net_stats;
  p_dev->set_multicast_list = &cpmac_main_multicast_set;
  /* to check: may this be done only when switch available? */
  p_dev->do_ioctl = &cpmac_main_ioctl;
# ifdef CPMAC_TX_TIMEOUT
  p_dev->tx_timeout = &cpmac_main_tx_timeout;
  p_dev->watchdog_timeo = 2 * HZ;
# endif
  p_dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST);
  p_dev->flags |= cpmac_main_setup_configuration (&cpmac_priv->capabilities);
  p_dev->tx_queue_len = 256;
  netif_carrier_off (p_dev);
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void cpmac_main_event_update (void) 
{
  unsigned int i;
  struct cpmac_event_struct *event_struct;

  if (avm_event_source_check_id (cpmac_event_handle, 
      avm_event_id_ethernet_connect_status)) {
    if (!(event_struct = (struct cpmac_event_struct *) kmalloc (sizeof (struct cpmac_event_struct), 
        GFP_ATOMIC))) {
      DEB_ERR("cpmac_main_event_update, nomem for status update\n");
    } else {
      event_struct->ports = cpmac_devices_installed;
      /* only the mac knows about number of registered phys */
      /* exception: in case of switch phy overwrites .ports */
      for (i=0; i<CPMAC_MAX_PHY; i++) {
        if (g_dev_array[i].p_dev) {
          g_dev_array[i].service_funcs.control_req (
            g_dev_array[i].phy_handle, CPMAC_CONTROL_REQ_UPDATE, event_struct);
        }
      }
      event_struct->event_header.id = avm_event_id_ethernet_connect_status;
      avm_event_source_trigger (cpmac_event_handle, avm_event_id_ethernet_connect_status, 
        sizeof(struct cpmac_event_struct), (void *) event_struct);
    }
  } else {
    DEB_TRC("cpmac_main_event_update, avm_event_source_check_id failed\n");
  }
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static void cpmac_main_event_update_detail (void) 
{
  unsigned int phy_id;
  struct cpmac_event_struct_verbose *event_struct;

  if (avm_event_source_check_id (cpmac_event_handle, 
      avm_event_id_ethernet_status)) {
    event_struct = (struct cpmac_event_struct_verbose *) kmalloc (sizeof (struct cpmac_event_struct_verbose), GFP_ATOMIC);
    if (!event_struct) {
      DEB_ERR("cpmac_main_event_update_detail, no mem for verbose status update\n");
    } else {
      if ((phy_id = cpmac_main_find_phy (1)) == CPMAC_MAX_PHY) {
        memset ((unsigned char *) event_struct, 0, sizeof (struct cpmac_event_struct_verbose));
      } else {
        g_dev_array[phy_id].service_funcs.control_req (
          g_dev_array[phy_id].phy_handle, CPMAC_CONTROL_REQ_UPDATE_DETAIL, event_struct);
      }
      event_struct->event_header.id = avm_event_id_ethernet_status;
      event_struct->chiptype = avm_event_ethernet_chip_adm6696l; 
      avm_event_source_trigger (cpmac_event_handle, avm_event_id_ethernet_status, 
        sizeof (struct cpmac_event_struct_verbose), (void *) event_struct);
    }
  } else {
    DEB_TRC("cpmac_main_event_update_detail, avm_event_source_check_id failed\n");
  }
}

/*------------------------------------------------------------------------------------------*\
  to check: lock anything (own entry point)
  update request from application
\*------------------------------------------------------------------------------------------*/
void cpmac_main_event_notify (void *context, enum _avm_event_id id)
{
  switch(id) {
    case avm_event_id_ethernet_connect_status:
      cpmac_main_event_update ();
      break;
    case avm_event_id_ethernet_status:
      cpmac_main_event_update_detail ();
      break;
    default:
      DEB_WARN("cpmac_main_event_notify, unhandled id %u!\n", id);
      break;
  }
}

/*------------------------------------------------------------------------------------------*\
  update indication from PHY
\*------------------------------------------------------------------------------------------*/
void cpmac_main_handle_event_update (cpmac_priv_t *cpmac_priv)
{
  cpmac_main_event_update ();
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
#ifdef CONFIG_IP_MULTICAST_FASTFORWARD

static struct tasklet_struct cpmac_mcfw_tasklet;
static struct sk_buff_head cpmac_mcfw_queue;

static void cpmac_mcfw_handle_tasklet(unsigned long data)
{
   struct sk_buff *skb;
   int i = 32;

   while (i-- > 0 && (skb = skb_dequeue(&cpmac_mcfw_queue)) != 0)
      dev_queue_xmit(skb);
   if (skb_queue_len(&cpmac_mcfw_queue))
	  tasklet_schedule(&cpmac_mcfw_tasklet);
}

static void cpmac_dev_queue_xmit(struct net_device *dev, struct sk_buff *skb)
{
   struct ethhdr *eth = skb->data;

   memcpy(eth->h_source, dev->dev_addr, ETH_ALEN);
   skb->dev = dev;

   if (irqs_disabled()) { /* tiatm is source */
	  skb_queue_tail(&cpmac_mcfw_queue, skb);
	  tasklet_schedule(&cpmac_mcfw_tasklet);
   } else {
      dev_queue_xmit(skb);
   }
}

static void cpmac_mc_transmit(void *privatedata, int sourceid,
                              mcfw_portset portset, struct sk_buff *skb)
{
    struct net_device *p_dev;
    cpmac_priv_t *cpmac_priv;

	p_dev = g_dev_array[0].p_dev;

	if (p_dev == 0) {
	   dev_kfree_skb_any(skb);
	   return;
	}
	cpmac_priv = (cpmac_priv_t *)netdev_priv (p_dev);
    if (cpmac_priv->enable_vlan) {
	   unsigned short tag;
	   int lastport = 0xff;
	   int port;
	   for (port = 0; port < 4; port++) {
          if (mcfw_portset_port_is_in_set(&portset, port)) {
			 if (lastport != 0xff) {
				struct sk_buff *nskb = skb_copy(skb, GFP_ATOMIC);
                tag = cpmac_priv->map_port_out[lastport];
				if (nskb && (nskb = __vlan_put_tag(nskb, tag)) != 0) {
	               cpmac_dev_queue_xmit(p_dev, nskb);
				}
			 }
			 lastport = port;
		  }
       }
	   if (lastport != 0xff) {
		  tag = cpmac_priv->map_port_out[lastport];
		  if ((skb = __vlan_put_tag(skb, tag)) != 0)  {
	         cpmac_dev_queue_xmit(p_dev, skb);
		  }
	   } else {
		  DEB_ERR("cpmac_mc_transmit: no port in portset\n");
		  dev_kfree_skb_any(skb);
	   }
	} else {
	   cpmac_dev_queue_xmit(p_dev, skb);
	}
}

static struct mcfw_netdriver _cpmac_mcfw_netdrv = {
   name: "cpmac",
   netdriver_mc_transmit: cpmac_mc_transmit,
   privatedata: (void *)0
};

static int cpmac_mcfw_init(void)
{
   int err;

   skb_queue_head_init(&cpmac_mcfw_queue);
   tasklet_init(&cpmac_mcfw_tasklet, cpmac_mcfw_handle_tasklet, 0);

   err = mcfw_netdriver_register(&_cpmac_mcfw_netdrv);
   if (err)
	  return err;
   cpmac_mcfw_netdrv = &_cpmac_mcfw_netdrv;
   cpmac_mcfw_sourceid = mcfw_multicast_forward_alloc_id(cpmac_mcfw_netdrv->name);
   DEB_INFO("cpmac_mcfw_init: done\n");
   return err;
}

static void cpmac_mcfw_exit(void)
{
   if (cpmac_mcfw_netdrv) {
      mcfw_netdriver_unregister(cpmac_mcfw_netdrv);
	  cpmac_mcfw_netdrv = 0;
      mcfw_multicast_forward_free_id(cpmac_mcfw_sourceid);
	  tasklet_kill(&cpmac_mcfw_tasklet);
      DEB_INFO("cpmac_mcfw_exit: done\n");
   }
}
#endif

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
static int __init cpmac_main_probe(void)
{
  DEB_INFO("cpmac_main_probe, init\n");

  cpmac_event_handle = avm_event_source_register("Ethernet status", 
    (1ull << avm_event_id_ethernet_status) | (1ull << avm_event_id_ethernet_connect_status),
    cpmac_main_event_notify, NULL);
  if (!cpmac_event_handle) {
    DEB_ERR("cpmac_main_probe: Could not register avm_event_source!\n");
  }
# ifdef CONFIG_IP_MULTICAST_FASTFORWARD
  if (cpmac_mcfw_init() != 0) {
    DEB_ERR("cpmac_main_probe: cpmac_mcfw_init: failed!\n");
  }
# endif
  return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
void cpmac_main_exit(void)
{
  struct net_device *dev;
  cpmac_priv_t *cpmac_priv;
  unsigned int i;

  DEB_INFO("cpmac_main_exit, init\n");
# ifdef CONFIG_IP_MULTICAST_FASTFORWARD
  cpmac_mcfw_exit();
# endif
  avm_event_source_release (cpmac_event_handle);

  for (i=0; i<CPMAC_MAX_PHY; i++) {
    if ((dev = g_dev_array[i].p_dev)) {
      cpmac_priv = (cpmac_priv_t*)netdev_priv (dev);
      cpmac_if_release (cpmac_priv);
    }
  }
}


module_init(cpmac_main_probe);
module_exit(cpmac_main_exit);
