packages: clean up the package folder
[openwrt/openwrt.git] / package / kernel / lantiq / ltq-ptm / src / ifxmips_ptm_adsl.c
diff --git a/package/kernel/lantiq/ltq-ptm/src/ifxmips_ptm_adsl.c b/package/kernel/lantiq/ltq-ptm/src/ifxmips_ptm_adsl.c
new file mode 100644 (file)
index 0000000..391d56c
--- /dev/null
@@ -0,0 +1,1551 @@
+/******************************************************************************
+**
+** FILE NAME    : ifxmips_ptm_adsl.c
+** PROJECT      : UEIP
+** MODULES      : PTM
+**
+** DATE         : 7 Jul 2009
+** AUTHOR       : Xu Liang
+** DESCRIPTION  : PTM driver common source file (core functions for Danube/
+**                Amazon-SE/AR9)
+** COPYRIGHT    :       Copyright (c) 2006
+**                      Infineon Technologies AG
+**                      Am Campeon 1-12, 85579 Neubiberg, Germany
+**
+**    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; either version 2 of the License, or
+**    (at your option) any later version.
+**
+** HISTORY
+** $Date        $Author         $Comment
+** 07 JUL 2009  Xu Liang        Init Version
+*******************************************************************************/
+
+
+
+/*
+ * ####################################
+ *              Head File
+ * ####################################
+ */
+
+/*
+ *  Common Head File
+ */
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/proc_fs.h>
+#include <linux/init.h>
+#include <linux/ioctl.h>
+#include <linux/etherdevice.h>
+#include <linux/interrupt.h>
+#include <asm/io.h>
+
+/*
+ *  Chip Specific Head File
+ */
+#include "ifxmips_ptm_adsl.h"
+
+
+#include <lantiq_soc.h>
+
+/*
+ * ####################################
+ *        Kernel Version Adaption
+ * ####################################
+ */
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11)
+  #define MODULE_PARM_ARRAY(a, b)   module_param_array(a, int, NULL, 0)
+  #define MODULE_PARM(a, b)         module_param(a, int, 0)
+#else
+  #define MODULE_PARM_ARRAY(a, b)   MODULE_PARM(a, b)
+#endif
+
+
+
+/*
+ * ####################################
+ *   Parameters to Configure PPE
+ * ####################################
+ */
+
+static int write_desc_delay     = 0x20;         /*  Write descriptor delay                          */
+
+static int rx_max_packet_size   = ETH_MAX_FRAME_LENGTH;
+                                                /*  Max packet size for RX                          */
+
+static int dma_rx_descriptor_length = 24;       /*  Number of descriptors per DMA RX channel        */
+static int dma_tx_descriptor_length = 24;       /*  Number of descriptors per DMA TX channel        */
+
+static int eth_efmtc_crc_cfg = 0x03100710;      /*  default: tx_eth_crc_check: 1, tx_tc_crc_check: 1, tx_tc_crc_len = 16    */
+                                                /*           rx_eth_crc_present: 1, rx_eth_crc_check: 1, rx_tc_crc_check: 1, rx_tc_crc_len = 16 */
+
+MODULE_PARM(write_desc_delay, "i");
+MODULE_PARM_DESC(write_desc_delay, "PPE core clock cycles between descriptor write and effectiveness in external RAM");
+
+MODULE_PARM(rx_max_packet_size, "i");
+MODULE_PARM_DESC(rx_max_packet_size, "Max packet size in byte for downstream ethernet frames");
+
+MODULE_PARM(dma_rx_descriptor_length, "i");
+MODULE_PARM_DESC(dma_rx_descriptor_length, "Number of descriptor assigned to DMA RX channel (>16)");
+MODULE_PARM(dma_tx_descriptor_length, "i");
+MODULE_PARM_DESC(dma_tx_descriptor_length, "Number of descriptor assigned to DMA TX channel (>16)");
+
+MODULE_PARM(eth_efmtc_crc_cfg, "i");
+MODULE_PARM_DESC(eth_efmtc_crc_cfg, "Configuration for PTM TX/RX ethernet/efm-tc CRC");
+
+
+
+/*
+ * ####################################
+ *              Definition
+ * ####################################
+ */
+
+
+#define DUMP_SKB_LEN                            ~0
+
+
+
+/*
+ * ####################################
+ *             Declaration
+ * ####################################
+ */
+
+/*
+ *  Network Operations
+ */
+static void ptm_setup(struct net_device *, int);
+static struct net_device_stats *ptm_get_stats(struct net_device *);
+static int ptm_open(struct net_device *);
+static int ptm_stop(struct net_device *);
+  static unsigned int ptm_poll(int, unsigned int);
+  static int ptm_napi_poll(struct napi_struct *, int);
+static int ptm_hard_start_xmit(struct sk_buff *, struct net_device *);
+static int ptm_ioctl(struct net_device *, struct ifreq *, int);
+static void ptm_tx_timeout(struct net_device *);
+
+/*
+ *  DSL Data LED
+ */
+static INLINE void adsl_led_flash(void);
+
+/*
+ *  buffer manage functions
+ */
+static INLINE struct sk_buff* alloc_skb_rx(void);
+//static INLINE struct sk_buff* alloc_skb_tx(unsigned int);
+static INLINE struct sk_buff *get_skb_rx_pointer(unsigned int);
+static INLINE int get_tx_desc(unsigned int, unsigned int *);
+
+/*
+ *  Mailbox handler and signal function
+ */
+static INLINE int mailbox_rx_irq_handler(unsigned int);
+static irqreturn_t mailbox_irq_handler(int, void *);
+static INLINE void mailbox_signal(unsigned int, int);
+#ifdef CONFIG_IFX_PTM_RX_TASKLET
+  static void do_ptm_tasklet(unsigned long);
+#endif
+
+/*
+ *  Debug Functions
+ */
+#if defined(DEBUG_DUMP_SKB) && DEBUG_DUMP_SKB
+  static void dump_skb(struct sk_buff *, u32, char *, int, int, int);
+#else
+  #define dump_skb(skb, len, title, port, ch, is_tx)    do {} while (0)
+#endif
+#if defined(ENABLE_DBG_PROC) && ENABLE_DBG_PROC
+  static void skb_swap(struct sk_buff *);
+#else
+  #define skb_swap(skb)                                 do {} while (0)
+#endif
+
+/*
+ *  Proc File Functions
+ */
+static INLINE void proc_file_create(void);
+static INLINE void proc_file_delete(void);
+static int proc_read_version(char *, char **, off_t, int, int *, void *);
+static int proc_read_wanmib(char *, char **, off_t, int, int *, void *);
+static int proc_write_wanmib(struct file *, const char *, unsigned long, void *);
+#if defined(ENABLE_FW_PROC) && ENABLE_FW_PROC
+  static int proc_read_genconf(char *, char **, off_t, int, int *, void *);
+#endif
+#if defined(ENABLE_DBG_PROC) && ENABLE_DBG_PROC
+  static int proc_read_dbg(char *, char **, off_t, int, int *, void *);
+  static int proc_write_dbg(struct file *, const char *, unsigned long, void *);
+#endif
+
+/*
+ *  Proc Help Functions
+ */
+static INLINE int stricmp(const char *, const char *);
+#if defined(ENABLE_DBG_PROC) && ENABLE_DBG_PROC
+  static INLINE int strincmp(const char *, const char *, int);
+#endif
+static INLINE int ifx_ptm_version(char *);
+
+/*
+ *  Init & clean-up functions
+ */
+static INLINE void check_parameters(void);
+static INLINE int init_priv_data(void);
+static INLINE void clear_priv_data(void);
+static INLINE void init_tables(void);
+
+/*
+ *  Exteranl Function
+ */
+#if defined(CONFIG_IFXMIPS_DSL_CPE_MEI) || defined(CONFIG_IFXMIPS_DSL_CPE_MEI_MODULE)
+  extern int ifx_mei_atm_showtime_check(int *is_showtime, struct port_cell_info *port_cell, void **xdata_addr);
+#else
+  static inline int ifx_mei_atm_showtime_check(int *is_showtime, struct port_cell_info *port_cell, void **xdata_addr)
+  {
+    if ( is_showtime != NULL )
+        *is_showtime = 0;
+    return 0;
+  }
+#endif
+
+/*
+ *  External variable
+ */
+#if defined(CONFIG_IFXMIPS_DSL_CPE_MEI) || defined(CONFIG_IFXMIPS_DSL_CPE_MEI_MODULE)
+  extern int (*ifx_mei_atm_showtime_enter)(struct port_cell_info *, void *);
+  extern int (*ifx_mei_atm_showtime_exit)(void);
+#else
+  int (*ifx_mei_atm_showtime_enter)(struct port_cell_info *, void *) = NULL;
+  EXPORT_SYMBOL(ifx_mei_atm_showtime_enter);
+  int (*ifx_mei_atm_showtime_exit)(void) = NULL;
+  EXPORT_SYMBOL(ifx_mei_atm_showtime_exit);
+#endif
+
+
+
+/*
+ * ####################################
+ *            Local Variable
+ * ####################################
+ */
+
+static struct ptm_priv_data g_ptm_priv_data;
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
+static struct net_device_ops g_ptm_netdev_ops = {
+    .ndo_get_stats       = ptm_get_stats,
+    .ndo_open            = ptm_open,
+    .ndo_stop            = ptm_stop,
+    .ndo_start_xmit      = ptm_hard_start_xmit,
+    .ndo_validate_addr   = eth_validate_addr,
+    .ndo_set_mac_address = eth_mac_addr,
+    .ndo_change_mtu      = eth_change_mtu,
+    .ndo_do_ioctl        = ptm_ioctl,
+    .ndo_tx_timeout      = ptm_tx_timeout,
+};
+#endif
+
+static struct net_device *g_net_dev[2] = {0};
+static char *g_net_dev_name[2] = {"ptm0", "ptmfast0"};
+
+#ifdef CONFIG_IFX_PTM_RX_TASKLET
+  static struct tasklet_struct g_ptm_tasklet[] = {
+    {NULL, 0, ATOMIC_INIT(0), do_ptm_tasklet, 0},
+    {NULL, 0, ATOMIC_INIT(0), do_ptm_tasklet, 1},
+  };
+#endif
+
+unsigned int ifx_ptm_dbg_enable = DBG_ENABLE_MASK_ERR;
+
+static struct proc_dir_entry* g_ptm_dir = NULL;
+
+static int g_showtime = 0;
+
+
+
+/*
+ * ####################################
+ *            Local Function
+ * ####################################
+ */
+
+static void ptm_setup(struct net_device *dev, int ndev)
+{
+    /*  hook network operations */
+    dev->netdev_ops      = &g_ptm_netdev_ops;
+    netif_napi_add(dev, &g_ptm_priv_data.itf[ndev].napi, ptm_napi_poll, 25);
+    dev->watchdog_timeo  = ETH_WATCHDOG_TIMEOUT;
+
+    dev->dev_addr[0] = 0x00;
+    dev->dev_addr[1] = 0x20;
+    dev->dev_addr[2] = 0xda;
+    dev->dev_addr[3] = 0x86;
+    dev->dev_addr[4] = 0x23;
+    dev->dev_addr[5] = 0x75 + ndev;
+}
+
+static struct net_device_stats *ptm_get_stats(struct net_device *dev)
+{
+    int ndev;
+
+    for ( ndev = 0; ndev < ARRAY_SIZE(g_net_dev) && g_net_dev[ndev] != dev; ndev++ );
+    ASSERT(ndev >= 0 && ndev < ARRAY_SIZE(g_net_dev), "ndev = %d (wrong value)", ndev);
+
+    g_ptm_priv_data.itf[ndev].stats.rx_errors   = WAN_MIB_TABLE[ndev].wrx_tccrc_err_pdu + WAN_MIB_TABLE[ndev].wrx_ethcrc_err_pdu;
+    g_ptm_priv_data.itf[ndev].stats.rx_dropped  = WAN_MIB_TABLE[ndev].wrx_nodesc_drop_pdu + WAN_MIB_TABLE[ndev].wrx_len_violation_drop_pdu + (WAN_MIB_TABLE[ndev].wrx_correct_pdu - g_ptm_priv_data.itf[ndev].stats.rx_packets);
+
+    return &g_ptm_priv_data.itf[ndev].stats;
+}
+
+static int ptm_open(struct net_device *dev)
+{
+    int ndev;
+
+    for ( ndev = 0; ndev < ARRAY_SIZE(g_net_dev) && g_net_dev[ndev] != dev; ndev++ );
+    ASSERT(ndev >= 0 && ndev < ARRAY_SIZE(g_net_dev), "ndev = %d (wrong value)", ndev);
+
+    napi_enable(&g_ptm_priv_data.itf[ndev].napi);
+
+    IFX_REG_W32_MASK(0, 1 << ndev, MBOX_IGU1_IER);
+
+    netif_start_queue(dev);
+
+    return 0;
+}
+
+static int ptm_stop(struct net_device *dev)
+{
+    int ndev;
+
+    for ( ndev = 0; ndev < ARRAY_SIZE(g_net_dev) && g_net_dev[ndev] != dev; ndev++ );
+    ASSERT(ndev >= 0 && ndev < ARRAY_SIZE(g_net_dev), "ndev = %d (wrong value)", ndev);
+
+    IFX_REG_W32_MASK((1 << ndev) | (1 << (ndev + 16)), 0, MBOX_IGU1_IER);
+
+    napi_disable(&g_ptm_priv_data.itf[ndev].napi);
+
+    netif_stop_queue(dev);
+
+    return 0;
+}
+
+static unsigned int ptm_poll(int ndev, unsigned int work_to_do)
+{
+    unsigned int work_done = 0;
+
+    ASSERT(ndev >= 0 && ndev < ARRAY_SIZE(g_net_dev), "ndev = %d (wrong value)", ndev);
+
+    while ( work_done < work_to_do && WRX_DMA_CHANNEL_CONFIG(ndev)->vlddes > 0 ) {
+        if ( mailbox_rx_irq_handler(ndev) < 0 )
+            break;
+
+        work_done++;
+    }
+
+    return work_done;
+}
+static int ptm_napi_poll(struct napi_struct *napi, int budget)
+{
+    int ndev;
+    unsigned int work_done;
+
+    for ( ndev = 0; ndev < ARRAY_SIZE(g_net_dev) && g_net_dev[ndev] != napi->dev; ndev++ );
+
+    work_done = ptm_poll(ndev, budget);
+
+    //  interface down
+    if ( !netif_running(napi->dev) ) {
+        napi_complete(napi);
+        return work_done;
+    }
+
+    //  no more traffic
+    if ( WRX_DMA_CHANNEL_CONFIG(ndev)->vlddes == 0 ) {
+        //  clear interrupt
+        IFX_REG_W32_MASK(0, 1 << ndev, MBOX_IGU1_ISRC);
+        //  double check
+        if ( WRX_DMA_CHANNEL_CONFIG(ndev)->vlddes == 0 ) {
+            napi_complete(napi);
+            IFX_REG_W32_MASK(0, 1 << ndev, MBOX_IGU1_IER);
+            return work_done;
+        }
+    }
+
+    //  next round
+    return work_done;
+}
+
+static int ptm_hard_start_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+    int ndev;
+    unsigned int f_full;
+    int desc_base;
+    register struct tx_descriptor reg_desc = {0};
+
+    for ( ndev = 0; ndev < ARRAY_SIZE(g_net_dev) && g_net_dev[ndev] != dev; ndev++ );
+    ASSERT(ndev >= 0 && ndev < ARRAY_SIZE(g_net_dev), "ndev = %d (wrong value)", ndev);
+
+    if ( !g_showtime ) {
+        err("not in showtime");
+        goto PTM_HARD_START_XMIT_FAIL;
+    }
+
+    /*  allocate descriptor */
+    desc_base = get_tx_desc(ndev, &f_full);
+    if ( f_full ) {
+        dev->trans_start = jiffies;
+        netif_stop_queue(dev);
+
+        IFX_REG_W32_MASK(0, 1 << (ndev + 16), MBOX_IGU1_ISRC);
+        IFX_REG_W32_MASK(0, 1 << (ndev + 16), MBOX_IGU1_IER);
+    }
+    if ( desc_base < 0 )
+        goto PTM_HARD_START_XMIT_FAIL;
+
+    if ( g_ptm_priv_data.itf[ndev].tx_skb[desc_base] != NULL )
+        dev_kfree_skb_any(g_ptm_priv_data.itf[ndev].tx_skb[desc_base]);
+    g_ptm_priv_data.itf[ndev].tx_skb[desc_base] = skb;
+
+    reg_desc.dataptr = (unsigned int)skb->data >> 2;
+    reg_desc.datalen = skb->len < ETH_ZLEN ? ETH_ZLEN : skb->len;
+    reg_desc.byteoff = (unsigned int)skb->data & (DATA_BUFFER_ALIGNMENT - 1);
+    reg_desc.own     = 1;
+    reg_desc.c       = 1;
+    reg_desc.sop = reg_desc.eop = 1;
+
+    /*  write discriptor to memory and write back cache */
+    g_ptm_priv_data.itf[ndev].tx_desc[desc_base] = reg_desc;
+    dma_cache_wback((unsigned long)skb->data, skb->len);
+    wmb();
+
+    dump_skb(skb, DUMP_SKB_LEN, (char *)__func__, ndev, ndev, 1);
+
+    if ( (ifx_ptm_dbg_enable & DBG_ENABLE_MASK_MAC_SWAP) ) {
+        skb_swap(skb);
+    }
+
+    g_ptm_priv_data.itf[ndev].stats.tx_packets++;
+    g_ptm_priv_data.itf[ndev].stats.tx_bytes += reg_desc.datalen;
+
+    dev->trans_start = jiffies;
+    mailbox_signal(ndev, 1);
+
+    adsl_led_flash();
+
+    return NETDEV_TX_OK;
+
+PTM_HARD_START_XMIT_FAIL:
+    dev_kfree_skb_any(skb);
+    g_ptm_priv_data.itf[ndev].stats.tx_dropped++;
+    return NETDEV_TX_OK;
+}
+
+static int ptm_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+    int ndev;
+
+    for ( ndev = 0; ndev < ARRAY_SIZE(g_net_dev) && g_net_dev[ndev] != dev; ndev++ );
+    ASSERT(ndev >= 0 && ndev < ARRAY_SIZE(g_net_dev), "ndev = %d (wrong value)", ndev);
+
+    switch ( cmd )
+    {
+    case IFX_PTM_MIB_CW_GET:
+        ((PTM_CW_IF_ENTRY_T *)ifr->ifr_data)->ifRxNoIdleCodewords   = WAN_MIB_TABLE[ndev].wrx_nonidle_cw;
+        ((PTM_CW_IF_ENTRY_T *)ifr->ifr_data)->ifRxIdleCodewords     = WAN_MIB_TABLE[ndev].wrx_idle_cw;
+        ((PTM_CW_IF_ENTRY_T *)ifr->ifr_data)->ifRxCodingViolation   = WAN_MIB_TABLE[ndev].wrx_err_cw;
+        ((PTM_CW_IF_ENTRY_T *)ifr->ifr_data)->ifTxNoIdleCodewords   = 0;
+        ((PTM_CW_IF_ENTRY_T *)ifr->ifr_data)->ifTxIdleCodewords     = 0;
+        break;
+    case IFX_PTM_MIB_FRAME_GET:
+        ((PTM_FRAME_MIB_T *)ifr->ifr_data)->RxCorrect   = WAN_MIB_TABLE[ndev].wrx_correct_pdu;
+        ((PTM_FRAME_MIB_T *)ifr->ifr_data)->TC_CrcError = WAN_MIB_TABLE[ndev].wrx_tccrc_err_pdu;
+        ((PTM_FRAME_MIB_T *)ifr->ifr_data)->RxDropped   = WAN_MIB_TABLE[ndev].wrx_nodesc_drop_pdu + WAN_MIB_TABLE[ndev].wrx_len_violation_drop_pdu;
+        ((PTM_FRAME_MIB_T *)ifr->ifr_data)->TxSend      = WAN_MIB_TABLE[ndev].wtx_total_pdu;
+        break;
+    case IFX_PTM_CFG_GET:
+        ((IFX_PTM_CFG_T *)ifr->ifr_data)->RxEthCrcPresent = CFG_ETH_EFMTC_CRC->rx_eth_crc_present;
+        ((IFX_PTM_CFG_T *)ifr->ifr_data)->RxEthCrcCheck   = CFG_ETH_EFMTC_CRC->rx_eth_crc_check;
+        ((IFX_PTM_CFG_T *)ifr->ifr_data)->RxTcCrcCheck    = CFG_ETH_EFMTC_CRC->rx_tc_crc_check;
+        ((IFX_PTM_CFG_T *)ifr->ifr_data)->RxTcCrcLen      = CFG_ETH_EFMTC_CRC->rx_tc_crc_len;
+        ((IFX_PTM_CFG_T *)ifr->ifr_data)->TxEthCrcGen     = CFG_ETH_EFMTC_CRC->tx_eth_crc_gen;
+        ((IFX_PTM_CFG_T *)ifr->ifr_data)->TxTcCrcGen      = CFG_ETH_EFMTC_CRC->tx_tc_crc_gen;
+        ((IFX_PTM_CFG_T *)ifr->ifr_data)->TxTcCrcLen      = CFG_ETH_EFMTC_CRC->tx_tc_crc_len;
+        break;
+    case IFX_PTM_CFG_SET:
+        CFG_ETH_EFMTC_CRC->rx_eth_crc_present   = ((IFX_PTM_CFG_T *)ifr->ifr_data)->RxEthCrcPresent ? 1 : 0;
+        CFG_ETH_EFMTC_CRC->rx_eth_crc_check     = ((IFX_PTM_CFG_T *)ifr->ifr_data)->RxEthCrcCheck ? 1 : 0;
+        if ( ((IFX_PTM_CFG_T *)ifr->ifr_data)->RxTcCrcCheck && (((IFX_PTM_CFG_T *)ifr->ifr_data)->RxTcCrcLen == 16 || ((IFX_PTM_CFG_T *)ifr->ifr_data)->RxTcCrcLen == 32) )
+        {
+            CFG_ETH_EFMTC_CRC->rx_tc_crc_check  = 1;
+            CFG_ETH_EFMTC_CRC->rx_tc_crc_len    = ((IFX_PTM_CFG_T *)ifr->ifr_data)->RxTcCrcLen;
+        }
+        else
+        {
+            CFG_ETH_EFMTC_CRC->rx_tc_crc_check  = 0;
+            CFG_ETH_EFMTC_CRC->rx_tc_crc_len    = 0;
+        }
+        CFG_ETH_EFMTC_CRC->tx_eth_crc_gen       = ((IFX_PTM_CFG_T *)ifr->ifr_data)->TxEthCrcGen ? 1 : 0;
+        if ( ((IFX_PTM_CFG_T *)ifr->ifr_data)->TxTcCrcGen && (((IFX_PTM_CFG_T *)ifr->ifr_data)->TxTcCrcLen == 16 || ((IFX_PTM_CFG_T *)ifr->ifr_data)->TxTcCrcLen == 32) )
+        {
+            CFG_ETH_EFMTC_CRC->tx_tc_crc_gen    = 1;
+            CFG_ETH_EFMTC_CRC->tx_tc_crc_len    = ((IFX_PTM_CFG_T *)ifr->ifr_data)->TxTcCrcLen;
+        }
+        else
+        {
+            CFG_ETH_EFMTC_CRC->tx_tc_crc_gen    = 0;
+            CFG_ETH_EFMTC_CRC->tx_tc_crc_len    = 0;
+        }
+        break;
+    default:
+        return -EOPNOTSUPP;
+    }
+
+    return 0;
+}
+
+static void ptm_tx_timeout(struct net_device *dev)
+{
+    int ndev;
+
+    for ( ndev = 0; ndev < ARRAY_SIZE(g_net_dev) && g_net_dev[ndev] != dev; ndev++ );
+    ASSERT(ndev >= 0 && ndev < ARRAY_SIZE(g_net_dev), "ndev = %d (wrong value)", ndev);
+
+    /*  disable TX irq, release skb when sending new packet */
+    IFX_REG_W32_MASK(1 << (ndev + 16), 0, MBOX_IGU1_IER);
+
+    /*  wake up TX queue    */
+    netif_wake_queue(dev);
+
+    return;
+}
+
+static INLINE void adsl_led_flash(void)
+{
+}
+
+static INLINE struct sk_buff* alloc_skb_rx(void)
+{
+    struct sk_buff *skb;
+
+    /*  allocate memroy including trailer and padding   */
+    skb = dev_alloc_skb(rx_max_packet_size + RX_HEAD_MAC_ADDR_ALIGNMENT + DATA_BUFFER_ALIGNMENT);
+    if ( skb != NULL ) {
+        /*  must be burst length alignment and reserve two more bytes for MAC address alignment  */
+        if ( ((unsigned int)skb->data & (DATA_BUFFER_ALIGNMENT - 1)) != 0 )
+            skb_reserve(skb, ~((unsigned int)skb->data + (DATA_BUFFER_ALIGNMENT - 1)) & (DATA_BUFFER_ALIGNMENT - 1));
+        /*  pub skb in reserved area "skb->data - 4"    */
+        *((struct sk_buff **)skb->data - 1) = skb;
+        wmb();
+        /*  write back and invalidate cache    */
+        dma_cache_wback_inv((unsigned long)skb->data - sizeof(skb), sizeof(skb));
+        /*  invalidate cache    */
+        dma_cache_inv((unsigned long)skb->data, (unsigned int)skb->end - (unsigned int)skb->data);
+    }
+
+    return skb;
+}
+
+#if 0
+static INLINE struct sk_buff* alloc_skb_tx(unsigned int size)
+{
+    struct sk_buff *skb;
+
+    /*  allocate memory including padding   */
+    size = (size + DATA_BUFFER_ALIGNMENT - 1) & ~(DATA_BUFFER_ALIGNMENT - 1);
+    skb = dev_alloc_skb(size + DATA_BUFFER_ALIGNMENT);
+    /*  must be burst length alignment  */
+    if ( skb != NULL )
+        skb_reserve(skb, ~((unsigned int)skb->data + (DATA_BUFFER_ALIGNMENT - 1)) & (DATA_BUFFER_ALIGNMENT - 1));
+    return skb;
+}
+#endif
+
+static INLINE struct sk_buff *get_skb_rx_pointer(unsigned int dataptr)
+{
+    unsigned int skb_dataptr;
+    struct sk_buff *skb;
+
+    skb_dataptr = ((dataptr - 1) << 2) | KSEG1;
+    skb = *(struct sk_buff **)skb_dataptr;
+
+    ASSERT((unsigned int)skb >= KSEG0, "invalid skb - skb = %#08x, dataptr = %#08x", (unsigned int)skb, dataptr);
+    ASSERT(((unsigned int)skb->data | KSEG1) == ((dataptr << 2) | KSEG1), "invalid skb - skb = %#08x, skb->data = %#08x, dataptr = %#08x", (unsigned int)skb, (unsigned int)skb->data, dataptr);
+
+    return skb;
+}
+
+static INLINE int get_tx_desc(unsigned int itf, unsigned int *f_full)
+{
+    int desc_base = -1;
+    struct ptm_itf *p_itf = &g_ptm_priv_data.itf[itf];
+
+    //  assume TX is serial operation
+    //  no protection provided
+
+    *f_full = 1;
+
+    if ( p_itf->tx_desc[p_itf->tx_desc_pos].own == 0 ) {
+        desc_base = p_itf->tx_desc_pos;
+        if ( ++(p_itf->tx_desc_pos) == dma_tx_descriptor_length )
+            p_itf->tx_desc_pos = 0;
+        if ( p_itf->tx_desc[p_itf->tx_desc_pos].own == 0 )
+            *f_full = 0;
+    }
+
+    return desc_base;
+}
+
+static INLINE int mailbox_rx_irq_handler(unsigned int ch)   //  return: < 0 - descriptor not available, 0 - received one packet
+{
+    unsigned int ndev = ch;
+    struct sk_buff *skb;
+    struct sk_buff *new_skb;
+    volatile struct rx_descriptor *desc;
+    struct rx_descriptor reg_desc;
+    int netif_rx_ret;
+
+    desc = &g_ptm_priv_data.itf[ndev].rx_desc[g_ptm_priv_data.itf[ndev].rx_desc_pos];
+    if ( desc->own || !desc->c )    //  if PP32 hold descriptor or descriptor not completed
+        return -EAGAIN;
+    if ( ++g_ptm_priv_data.itf[ndev].rx_desc_pos == dma_rx_descriptor_length )
+        g_ptm_priv_data.itf[ndev].rx_desc_pos = 0;
+
+    reg_desc = *desc;
+    skb = get_skb_rx_pointer(reg_desc.dataptr);
+
+    if ( !reg_desc.err ) {
+        new_skb = alloc_skb_rx();
+        if ( new_skb != NULL ) {
+            skb_reserve(skb, reg_desc.byteoff);
+            skb_put(skb, reg_desc.datalen);
+
+            dump_skb(skb, DUMP_SKB_LEN, (char *)__func__, ndev, ndev, 0);
+
+            //  parse protocol header
+            skb->dev = g_net_dev[ndev];
+            skb->protocol = eth_type_trans(skb, skb->dev);
+
+            g_net_dev[ndev]->last_rx = jiffies;
+
+            netif_rx_ret = netif_receive_skb(skb);
+
+            if ( netif_rx_ret != NET_RX_DROP ) {
+                g_ptm_priv_data.itf[ndev].stats.rx_packets++;
+                g_ptm_priv_data.itf[ndev].stats.rx_bytes += reg_desc.datalen;
+            }
+
+            reg_desc.dataptr = ((unsigned int)new_skb->data >> 2) & 0x0FFFFFFF;
+            reg_desc.byteoff = RX_HEAD_MAC_ADDR_ALIGNMENT;
+        }
+    }
+    else
+        reg_desc.err = 0;
+
+    reg_desc.datalen = rx_max_packet_size;
+    reg_desc.own     = 1;
+    reg_desc.c       = 0;
+
+    //  update descriptor
+    *desc = reg_desc;
+    wmb();
+
+    mailbox_signal(ndev, 0);
+
+    adsl_led_flash();
+
+    return 0;
+}
+
+static irqreturn_t mailbox_irq_handler(int irq, void *dev_id)
+{
+    unsigned int isr;
+    int i;
+
+    isr = IFX_REG_R32(MBOX_IGU1_ISR);
+    IFX_REG_W32(isr, MBOX_IGU1_ISRC);
+    isr &= IFX_REG_R32(MBOX_IGU1_IER);
+
+    while ( (i = __fls(isr)) >= 0 ) {
+        isr ^= 1 << i;
+
+        if ( i >= 16 ) {
+            //  TX
+            IFX_REG_W32_MASK(1 << i, 0, MBOX_IGU1_IER);
+            i -= 16;
+            if ( i < MAX_ITF_NUMBER )
+                netif_wake_queue(g_net_dev[i]);
+        }
+        else {
+            //  RX
+#ifdef CONFIG_IFX_PTM_RX_INTERRUPT
+            while ( WRX_DMA_CHANNEL_CONFIG(i)->vlddes > 0 )
+                mailbox_rx_irq_handler(i);
+#else
+            IFX_REG_W32_MASK(1 << i, 0, MBOX_IGU1_IER);
+            napi_schedule(&g_ptm_priv_data.itf[i].napi);
+#endif
+        }
+    }
+
+    return IRQ_HANDLED;
+}
+
+static INLINE void mailbox_signal(unsigned int itf, int is_tx)
+{
+    int count = 1000;
+
+    if ( is_tx ) {
+        while ( MBOX_IGU3_ISR_ISR(itf + 16) && count > 0 )
+            count--;
+        IFX_REG_W32(MBOX_IGU3_ISRS_SET(itf + 16), MBOX_IGU3_ISRS);
+    }
+    else {
+        while ( MBOX_IGU3_ISR_ISR(itf) && count > 0 )
+            count--;
+        IFX_REG_W32(MBOX_IGU3_ISRS_SET(itf), MBOX_IGU3_ISRS);
+    }
+
+    ASSERT(count != 0, "MBOX_IGU3_ISR = 0x%08x", IFX_REG_R32(MBOX_IGU3_ISR));
+}
+
+#ifdef CONFIG_IFX_PTM_RX_TASKLET
+static void do_ptm_tasklet(unsigned long arg)
+{
+    unsigned int work_to_do = 25;
+    unsigned int work_done = 0;
+
+    ASSERT(arg >= 0 && arg < ARRAY_SIZE(g_net_dev), "arg = %lu (wrong value)", arg);
+
+    while ( work_done < work_to_do && WRX_DMA_CHANNEL_CONFIG(arg)->vlddes > 0 ) {
+        if ( mailbox_rx_irq_handler(arg) < 0 )
+            break;
+
+        work_done++;
+    }
+
+    //  interface down
+    if ( !netif_running(g_net_dev[arg]) )
+        return;
+
+    //  no more traffic
+    if ( WRX_DMA_CHANNEL_CONFIG(arg)->vlddes == 0 ) {
+        //  clear interrupt
+        IFX_REG_W32_MASK(0, 1 << arg, MBOX_IGU1_ISRC);
+        //  double check
+        if ( WRX_DMA_CHANNEL_CONFIG(arg)->vlddes == 0 ) {
+            IFX_REG_W32_MASK(0, 1 << arg, MBOX_IGU1_IER);
+            return;
+        }
+    }
+
+    //  next round
+    tasklet_schedule(&g_ptm_tasklet[arg]);
+}
+#endif
+
+#if defined(DEBUG_DUMP_SKB) && DEBUG_DUMP_SKB
+static void dump_skb(struct sk_buff *skb, u32 len, char *title, int port, int ch, int is_tx)
+{
+    int i;
+
+    if ( !(ifx_ptm_dbg_enable & (is_tx ? DBG_ENABLE_MASK_DUMP_SKB_TX : DBG_ENABLE_MASK_DUMP_SKB_RX)) )
+        return;
+
+    if ( skb->len < len )
+        len = skb->len;
+
+    if ( len > rx_max_packet_size ) {
+        printk("too big data length: skb = %08x, skb->data = %08x, skb->len = %d\n", (u32)skb, (u32)skb->data, skb->len);
+        return;
+    }
+
+    if ( ch >= 0 )
+        printk("%s (port %d, ch %d)\n", title, port, ch);
+    else
+        printk("%s\n", title);
+    printk("  skb->data = %08X, skb->tail = %08X, skb->len = %d\n", (u32)skb->data, (u32)skb->tail, (int)skb->len);
+    for ( i = 1; i <= len; i++ ) {
+        if ( i % 16 == 1 )
+            printk("  %4d:", i - 1);
+        printk(" %02X", (int)(*((char*)skb->data + i - 1) & 0xFF));
+        if ( i % 16 == 0 )
+            printk("\n");
+    }
+    if ( (i - 1) % 16 != 0 )
+        printk("\n");
+}
+#endif
+
+#if defined(ENABLE_DBG_PROC) && ENABLE_DBG_PROC
+static void skb_swap(struct sk_buff *skb)
+{
+    unsigned char tmp[8];
+    unsigned char *p = skb->data;
+
+    if ( !(p[0] & 0x01) ) { //  bypass broadcast/multicast
+        //  swap MAC
+        memcpy(tmp, p, 6);
+        memcpy(p, p + 6, 6);
+        memcpy(p + 6, tmp, 6);
+        p += 12;
+
+        //  bypass VLAN
+        while ( p[0] == 0x81 && p[1] == 0x00 )
+            p += 4;
+
+        //  IP
+        if ( p[0] == 0x08 && p[1] == 0x00 ) {
+            p += 14;
+            memcpy(tmp, p, 4);
+            memcpy(p, p + 4, 4);
+            memcpy(p + 4, tmp, 4);
+            p += 8;
+        }
+
+        dma_cache_wback((unsigned long)skb->data, (unsigned long)p - (unsigned long)skb->data);
+    }
+}
+#endif
+
+static INLINE void proc_file_create(void)
+{
+    struct proc_dir_entry *res;
+
+    g_ptm_dir = proc_mkdir("driver/ifx_ptm", NULL);
+
+    create_proc_read_entry("version",
+                            0,
+                            g_ptm_dir,
+                            proc_read_version,
+                            NULL);
+
+    res = create_proc_entry("wanmib",
+                            0,
+                            g_ptm_dir);
+    if ( res != NULL ) {
+        res->read_proc  = proc_read_wanmib;
+        res->write_proc = proc_write_wanmib;
+    }
+
+#if defined(ENABLE_FW_PROC) && ENABLE_FW_PROC
+    create_proc_read_entry("genconf",
+                            0,
+                            g_ptm_dir,
+                            proc_read_genconf,
+                            NULL);
+
+  #ifdef CONFIG_AR9
+    create_proc_read_entry("regs",
+                            0,
+                            g_ptm_dir,
+                            ifx_ptm_proc_read_regs,
+                            NULL);
+  #endif
+#endif
+
+#if defined(ENABLE_DBG_PROC) && ENABLE_DBG_PROC
+    res = create_proc_entry("dbg",
+                            0,
+                            g_ptm_dir);
+    if ( res != NULL ) {
+        res->read_proc  = proc_read_dbg;
+        res->write_proc = proc_write_dbg;
+    }
+#endif
+}
+
+static INLINE void proc_file_delete(void)
+{
+#if defined(ENABLE_DBG_PROC) && ENABLE_DBG_PROC
+    remove_proc_entry("dbg", g_ptm_dir);
+#endif
+
+#if defined(ENABLE_FW_PROC) && ENABLE_FW_PROC
+  #ifdef CONFIG_AR9
+    remove_proc_entry("regs", g_ptm_dir);
+  #endif
+
+    remove_proc_entry("genconf", g_ptm_dir);
+#endif
+
+    remove_proc_entry("wanmib", g_ptm_dir);
+
+    remove_proc_entry("version", g_ptm_dir);
+
+    remove_proc_entry("driver/ifx_ptm", NULL);
+}
+
+static int proc_read_version(char *buf, char **start, off_t offset, int count, int *eof, void *data)
+{
+    int len = 0;
+
+    len += ifx_ptm_version(buf + len);
+
+    if ( offset >= len ) {
+        *start = buf;
+        *eof = 1;
+        return 0;
+    }
+    *start = buf + offset;
+    if ( (len -= offset) > count )
+        return count;
+    *eof = 1;
+    return len;
+}
+
+static int proc_read_wanmib(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+    int len = 0;
+    int i;
+    char *title[] = {
+        "ptm0\n",
+        "ptmfast0\n"
+    };
+
+    for ( i = 0; i < ARRAY_SIZE(title); i++ ) {
+        len += sprintf(page + off + len, title[i]);
+        len += sprintf(page + off + len, "  wrx_correct_pdu            = %d\n", WAN_MIB_TABLE[i].wrx_correct_pdu);
+        len += sprintf(page + off + len, "  wrx_correct_pdu_bytes      = %d\n", WAN_MIB_TABLE[i].wrx_correct_pdu_bytes);
+        len += sprintf(page + off + len, "  wrx_tccrc_err_pdu          = %d\n", WAN_MIB_TABLE[i].wrx_tccrc_err_pdu);
+        len += sprintf(page + off + len, "  wrx_tccrc_err_pdu_bytes    = %d\n", WAN_MIB_TABLE[i].wrx_tccrc_err_pdu_bytes);
+        len += sprintf(page + off + len, "  wrx_ethcrc_err_pdu         = %d\n", WAN_MIB_TABLE[i].wrx_ethcrc_err_pdu);
+        len += sprintf(page + off + len, "  wrx_ethcrc_err_pdu_bytes   = %d\n", WAN_MIB_TABLE[i].wrx_ethcrc_err_pdu_bytes);
+        len += sprintf(page + off + len, "  wrx_nodesc_drop_pdu        = %d\n", WAN_MIB_TABLE[i].wrx_nodesc_drop_pdu);
+        len += sprintf(page + off + len, "  wrx_len_violation_drop_pdu = %d\n", WAN_MIB_TABLE[i].wrx_len_violation_drop_pdu);
+        len += sprintf(page + off + len, "  wrx_idle_bytes             = %d\n", WAN_MIB_TABLE[i].wrx_idle_bytes);
+        len += sprintf(page + off + len, "  wrx_nonidle_cw             = %d\n", WAN_MIB_TABLE[i].wrx_nonidle_cw);
+        len += sprintf(page + off + len, "  wrx_idle_cw                = %d\n", WAN_MIB_TABLE[i].wrx_idle_cw);
+        len += sprintf(page + off + len, "  wrx_err_cw                 = %d\n", WAN_MIB_TABLE[i].wrx_err_cw);
+        len += sprintf(page + off + len, "  wtx_total_pdu              = %d\n", WAN_MIB_TABLE[i].wtx_total_pdu);
+        len += sprintf(page + off + len, "  wtx_total_bytes            = %d\n", WAN_MIB_TABLE[i].wtx_total_bytes);
+    }
+
+    *eof = 1;
+
+    return len;
+}
+
+static int proc_write_wanmib(struct file *file, const char *buf, unsigned long count, void *data)
+{
+    char str[2048];
+    char *p;
+    int len, rlen;
+
+    int i;
+
+    len = count < sizeof(str) ? count : sizeof(str) - 1;
+    rlen = len - copy_from_user(str, buf, len);
+    while ( rlen && str[rlen - 1] <= ' ' )
+        rlen--;
+    str[rlen] = 0;
+    for ( p = str; *p && *p <= ' '; p++, rlen-- );
+    if ( !*p )
+        return count;
+
+    if ( stricmp(p, "clear") == 0 || stricmp(p, "clean") == 0 ) {
+        for ( i = 0; i < 2; i++ )
+            memset((void*)&WAN_MIB_TABLE[i], 0, sizeof(WAN_MIB_TABLE[i]));
+    }
+
+    return count;
+}
+
+#if defined(ENABLE_FW_PROC) && ENABLE_FW_PROC
+
+static int proc_read_genconf(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+    int len = 0;
+    int len_max = off + count;
+    char *pstr;
+    char str[2048];
+    int llen = 0;
+    int i;
+    unsigned long bit;
+
+    pstr = *start = page;
+
+    __sync();
+
+    llen += sprintf(str + llen, "CFG_WAN_WRDES_DELAY (0x%08X): %d\n", (unsigned int)CFG_WAN_WRDES_DELAY, IFX_REG_R32(CFG_WAN_WRDES_DELAY));
+    llen += sprintf(str + llen, "CFG_WRX_DMACH_ON    (0x%08X):", (unsigned int)CFG_WRX_DMACH_ON);
+    for ( i = 0, bit = 1; i < MAX_RX_DMA_CHANNEL_NUMBER; i++, bit <<= 1 )
+        llen += sprintf(str + llen, " %d - %s", i, (IFX_REG_R32(CFG_WRX_DMACH_ON) & bit) ? "on " : "off");
+    llen += sprintf(str + llen, "\n");
+    llen += sprintf(str + llen, "CFG_WTX_DMACH_ON    (0x%08X):", (unsigned int)CFG_WTX_DMACH_ON);
+    for ( i = 0, bit = 1; i < MAX_TX_DMA_CHANNEL_NUMBER; i++, bit <<= 1 )
+        llen += sprintf(str + llen, " %d - %s", i, (IFX_REG_R32(CFG_WTX_DMACH_ON) & bit) ? "on " : "off");
+    llen += sprintf(str + llen, "\n");
+    llen += sprintf(str + llen, "CFG_WRX_LOOK_BITTH  (0x%08X): %d\n", (unsigned int)CFG_WRX_LOOK_BITTH, IFX_REG_R32(CFG_WRX_LOOK_BITTH));
+    llen += sprintf(str + llen, "CFG_ETH_EFMTC_CRC   (0x%08X): rx_tc_crc_len    - %2d,  rx_tc_crc_check    - %s\n", (unsigned int)CFG_ETH_EFMTC_CRC, CFG_ETH_EFMTC_CRC->rx_tc_crc_len, CFG_ETH_EFMTC_CRC->rx_tc_crc_check ? " on" : "off");
+    llen += sprintf(str + llen, "                                  rx_eth_crc_check - %s, rx_eth_crc_present - %s\n",   CFG_ETH_EFMTC_CRC->rx_eth_crc_check ? " on" : "off", CFG_ETH_EFMTC_CRC->rx_eth_crc_present ? " on" : "off");
+    llen += sprintf(str + llen, "                                  tx_tc_crc_len    - %2d,  tx_tc_crc_gen      - %s\n", CFG_ETH_EFMTC_CRC->tx_tc_crc_len, CFG_ETH_EFMTC_CRC->tx_tc_crc_gen ? " on" : "off");
+    llen += sprintf(str + llen, "                                  tx_eth_crc_gen   - %s\n", CFG_ETH_EFMTC_CRC->tx_eth_crc_gen ? " on" : "off");
+
+    llen += sprintf(str + llen, "RX Port:\n");
+    for ( i = 0; i < MAX_RX_DMA_CHANNEL_NUMBER; i++ )
+        llen += sprintf(str + llen, "  %d (0x%08X). mfs - %5d, dmach - %d, local_state - %d, partner_state - %d\n", i, (unsigned int)WRX_PORT_CONFIG(i), WRX_PORT_CONFIG(i)->mfs, WRX_PORT_CONFIG(i)->dmach, WRX_PORT_CONFIG(i)->local_state, WRX_PORT_CONFIG(i)->partner_state);
+    llen += sprintf(str + llen, "RX DMA Channel:\n");
+    for ( i = 0; i < MAX_RX_DMA_CHANNEL_NUMBER; i++ )
+        llen += sprintf(str + llen, "  %d (0x%08X). desba - 0x%08X (0x%08X), deslen - %d, vlddes - %d\n", i, (unsigned int)WRX_DMA_CHANNEL_CONFIG(i), WRX_DMA_CHANNEL_CONFIG(i)->desba, ((unsigned int)WRX_DMA_CHANNEL_CONFIG(i)->desba << 2) | KSEG1, WRX_DMA_CHANNEL_CONFIG(i)->deslen, WRX_DMA_CHANNEL_CONFIG(i)->vlddes);
+
+    llen += sprintf(str + llen, "TX Port:\n");
+    for ( i = 0; i < MAX_TX_DMA_CHANNEL_NUMBER; i++ )
+        llen += sprintf(str + llen, "  %d (0x%08X). tx_cwth2 - %d, tx_cwth1 - %d\n", i, (unsigned int)WTX_PORT_CONFIG(i), WTX_PORT_CONFIG(i)->tx_cwth2, WTX_PORT_CONFIG(i)->tx_cwth1);
+    llen += sprintf(str + llen, "TX DMA Channel:\n");
+    for ( i = 0; i < MAX_TX_DMA_CHANNEL_NUMBER; i++ )
+        llen += sprintf(str + llen, "  %d (0x%08X). desba - 0x%08X (0x%08X), deslen - %d, vlddes - %d\n", i, (unsigned int)WTX_DMA_CHANNEL_CONFIG(i), WTX_DMA_CHANNEL_CONFIG(i)->desba, ((unsigned int)WTX_DMA_CHANNEL_CONFIG(i)->desba << 2) | KSEG1, WTX_DMA_CHANNEL_CONFIG(i)->deslen, WTX_DMA_CHANNEL_CONFIG(i)->vlddes);
+
+    if ( len <= off && len + llen > off )
+    {
+        memcpy(pstr, str + off - len, len + llen - off);
+        pstr += len + llen - off;
+    }
+    else if ( len > off )
+    {
+        memcpy(pstr, str, llen);
+        pstr += llen;
+    }
+    len += llen;
+    if ( len >= len_max )
+        goto PROC_READ_GENCONF_OVERRUN_END;
+
+    *eof = 1;
+
+    return len - off;
+
+PROC_READ_GENCONF_OVERRUN_END:
+    return len - llen - off;
+}
+
+#endif  //  defined(ENABLE_FW_PROC) && ENABLE_FW_PROC
+
+#if defined(ENABLE_DBG_PROC) && ENABLE_DBG_PROC
+
+static int proc_read_dbg(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+    int len = 0;
+
+    len += sprintf(page + off + len, "error print      - %s\n", (ifx_ptm_dbg_enable & DBG_ENABLE_MASK_ERR)              ? "enabled" : "disabled");
+    len += sprintf(page + off + len, "debug print      - %s\n", (ifx_ptm_dbg_enable & DBG_ENABLE_MASK_DEBUG_PRINT)      ? "enabled" : "disabled");
+    len += sprintf(page + off + len, "assert           - %s\n", (ifx_ptm_dbg_enable & DBG_ENABLE_MASK_ASSERT)           ? "enabled" : "disabled");
+    len += sprintf(page + off + len, "dump rx skb      - %s\n", (ifx_ptm_dbg_enable & DBG_ENABLE_MASK_DUMP_SKB_RX)      ? "enabled" : "disabled");
+    len += sprintf(page + off + len, "dump tx skb      - %s\n", (ifx_ptm_dbg_enable & DBG_ENABLE_MASK_DUMP_SKB_TX)      ? "enabled" : "disabled");
+    len += sprintf(page + off + len, "mac swap         - %s\n", (ifx_ptm_dbg_enable & DBG_ENABLE_MASK_MAC_SWAP)         ? "enabled" : "disabled");
+
+    *eof = 1;
+
+    return len;
+}
+
+static int proc_write_dbg(struct file *file, const char *buf, unsigned long count, void *data)
+{
+    static const char *dbg_enable_mask_str[] = {
+        " error print",
+        " err",
+        " debug print",
+        " dbg",
+        " assert",
+        " assert",
+        " dump rx skb",
+        " rx",
+        " dump tx skb",
+        " tx",
+        " dump init",
+        " init",
+        " dump qos",
+        " qos",
+        " mac swap",
+        " swap",
+        " all"
+    };
+    static const int dbg_enable_mask_str_len[] = {
+        12, 4,
+        12, 4,
+        7,  7,
+        12, 3,
+        12, 3,
+        10, 5,
+        9,  4,
+        9,  5,
+        4
+    };
+    unsigned int dbg_enable_mask[] = {
+        DBG_ENABLE_MASK_ERR,
+        DBG_ENABLE_MASK_DEBUG_PRINT,
+        DBG_ENABLE_MASK_ASSERT,
+        DBG_ENABLE_MASK_DUMP_SKB_RX,
+        DBG_ENABLE_MASK_DUMP_SKB_TX,
+        DBG_ENABLE_MASK_DUMP_INIT,
+        DBG_ENABLE_MASK_DUMP_QOS,
+        DBG_ENABLE_MASK_MAC_SWAP,
+        DBG_ENABLE_MASK_ALL
+    };
+
+    char str[2048];
+    char *p;
+
+    int len, rlen;
+
+    int f_enable = 0;
+    int i;
+
+    len = count < sizeof(str) ? count : sizeof(str) - 1;
+    rlen = len - copy_from_user(str, buf, len);
+    while ( rlen && str[rlen - 1] <= ' ' )
+        rlen--;
+    str[rlen] = 0;
+    for ( p = str; *p && *p <= ' '; p++, rlen-- );
+    if ( !*p )
+        return 0;
+
+    //  debugging feature for enter/leave showtime
+    if ( strincmp(p, "enter", 5) == 0 && ifx_mei_atm_showtime_enter != NULL )
+        ifx_mei_atm_showtime_enter(NULL, NULL);
+    else if ( strincmp(p, "leave", 5) == 0 && ifx_mei_atm_showtime_exit != NULL )
+        ifx_mei_atm_showtime_exit();
+
+    if ( strincmp(p, "enable", 6) == 0 ) {
+        p += 6;
+        f_enable = 1;
+    }
+    else if ( strincmp(p, "disable", 7) == 0 ) {
+        p += 7;
+        f_enable = -1;
+    }
+    else if ( strincmp(p, "help", 4) == 0 || *p == '?' ) {
+        printk("echo <enable/disable> [err/dbg/assert/rx/tx/init/qos/swap/all] > /proc/driver/ifx_ptm/dbg\n");
+    }
+
+    if ( f_enable ) {
+        if ( *p == 0 ) {
+            if ( f_enable > 0 )
+                ifx_ptm_dbg_enable |= DBG_ENABLE_MASK_ALL & ~DBG_ENABLE_MASK_MAC_SWAP;
+            else
+                ifx_ptm_dbg_enable &= ~DBG_ENABLE_MASK_ALL | DBG_ENABLE_MASK_MAC_SWAP;
+        }
+        else {
+            do {
+                for ( i = 0; i < ARRAY_SIZE(dbg_enable_mask_str); i++ )
+                    if ( strincmp(p, dbg_enable_mask_str[i], dbg_enable_mask_str_len[i]) == 0 ) {
+                        if ( f_enable > 0 )
+                            ifx_ptm_dbg_enable |= dbg_enable_mask[i >> 1];
+                        else
+                            ifx_ptm_dbg_enable &= ~dbg_enable_mask[i >> 1];
+                        p += dbg_enable_mask_str_len[i];
+                        break;
+                    }
+            } while ( i < ARRAY_SIZE(dbg_enable_mask_str) );
+        }
+    }
+
+    return count;
+}
+
+#endif  //  defined(ENABLE_DBG_PROC) && ENABLE_DBG_PROC
+
+static INLINE int stricmp(const char *p1, const char *p2)
+{
+    int c1, c2;
+
+    while ( *p1 && *p2 )
+    {
+        c1 = *p1 >= 'A' && *p1 <= 'Z' ? *p1 + 'a' - 'A' : *p1;
+        c2 = *p2 >= 'A' && *p2 <= 'Z' ? *p2 + 'a' - 'A' : *p2;
+        if ( (c1 -= c2) )
+            return c1;
+        p1++;
+        p2++;
+    }
+
+    return *p1 - *p2;
+}
+
+#if defined(ENABLE_DBG_PROC) && ENABLE_DBG_PROC
+static INLINE int strincmp(const char *p1, const char *p2, int n)
+{
+    int c1 = 0, c2;
+
+    while ( n && *p1 && *p2 )
+    {
+        c1 = *p1 >= 'A' && *p1 <= 'Z' ? *p1 + 'a' - 'A' : *p1;
+        c2 = *p2 >= 'A' && *p2 <= 'Z' ? *p2 + 'a' - 'A' : *p2;
+        if ( (c1 -= c2) )
+            return c1;
+        p1++;
+        p2++;
+        n--;
+    }
+
+    return n ? *p1 - *p2 : c1;
+}
+#endif
+
+static INLINE int ifx_ptm_version(char *buf)
+{
+    int len = 0;
+    unsigned int major, minor;
+
+    ifx_ptm_get_fw_ver(&major, &minor);
+
+    len += sprintf(buf + len, "PTM %d.%d.%d", IFX_PTM_VER_MAJOR, IFX_PTM_VER_MID, IFX_PTM_VER_MINOR);
+    len += sprintf(buf + len, "    PTM (E1) firmware version %d.%d\n", major, minor);
+
+    return len;
+}
+
+static INLINE void check_parameters(void)
+{
+    /*  There is a delay between PPE write descriptor and descriptor is       */
+    /*  really stored in memory. Host also has this delay when writing        */
+    /*  descriptor. So PPE will use this value to determine if the write      */
+    /*  operation makes effect.                                               */
+    if ( write_desc_delay < 0 )
+        write_desc_delay = 0;
+
+    /*  Because of the limitation of length field in descriptors, the packet  */
+    /*  size could not be larger than 64K minus overhead size.                */
+    if ( rx_max_packet_size < ETH_MIN_FRAME_LENGTH )
+        rx_max_packet_size = ETH_MIN_FRAME_LENGTH;
+    else if ( rx_max_packet_size > 65536 - 1 )
+        rx_max_packet_size = 65536 - 1;
+
+    if ( dma_rx_descriptor_length < 2 )
+        dma_rx_descriptor_length = 2;
+    if ( dma_tx_descriptor_length < 2 )
+        dma_tx_descriptor_length = 2;
+}
+
+static INLINE int init_priv_data(void)
+{
+    void *p;
+    int i;
+    struct rx_descriptor rx_desc = {0};
+    struct sk_buff *skb;
+    volatile struct rx_descriptor *p_rx_desc;
+    volatile struct tx_descriptor *p_tx_desc;
+    struct sk_buff **ppskb;
+
+    //  clear ptm private data structure
+    memset(&g_ptm_priv_data, 0, sizeof(g_ptm_priv_data));
+
+    //  allocate memory for RX descriptors
+    p = kzalloc(MAX_ITF_NUMBER * dma_rx_descriptor_length * sizeof(struct rx_descriptor) + DESC_ALIGNMENT, GFP_KERNEL);
+    if ( p == NULL )
+        return -1;
+    dma_cache_inv((unsigned long)p, MAX_ITF_NUMBER * dma_rx_descriptor_length * sizeof(struct rx_descriptor) + DESC_ALIGNMENT);
+    g_ptm_priv_data.rx_desc_base = p;
+    //p = (void *)((((unsigned int)p + DESC_ALIGNMENT - 1) & ~(DESC_ALIGNMENT - 1)) | KSEG1);
+
+    //  allocate memory for TX descriptors
+    p = kzalloc(MAX_ITF_NUMBER * dma_tx_descriptor_length * sizeof(struct tx_descriptor) + DESC_ALIGNMENT, GFP_KERNEL);
+    if ( p == NULL )
+        return -1;
+    dma_cache_inv((unsigned long)p, MAX_ITF_NUMBER * dma_tx_descriptor_length * sizeof(struct tx_descriptor) + DESC_ALIGNMENT);
+    g_ptm_priv_data.tx_desc_base = p;
+
+    //  allocate memroy for TX skb pointers
+    p = kzalloc(MAX_ITF_NUMBER * dma_tx_descriptor_length * sizeof(struct sk_buff *) + 4, GFP_KERNEL);
+    if ( p == NULL )
+        return -1;
+    dma_cache_wback_inv((unsigned long)p, MAX_ITF_NUMBER * dma_tx_descriptor_length * sizeof(struct sk_buff *) + 4);
+    g_ptm_priv_data.tx_skb_base = p;
+
+    p_rx_desc = (volatile struct rx_descriptor *)((((unsigned int)g_ptm_priv_data.rx_desc_base + DESC_ALIGNMENT - 1) & ~(DESC_ALIGNMENT - 1)) | KSEG1);
+    p_tx_desc = (volatile struct tx_descriptor *)((((unsigned int)g_ptm_priv_data.tx_desc_base + DESC_ALIGNMENT - 1) & ~(DESC_ALIGNMENT - 1)) | KSEG1);
+    ppskb = (struct sk_buff **)(((unsigned int)g_ptm_priv_data.tx_skb_base + 3) & ~3);
+    for ( i = 0; i < MAX_ITF_NUMBER; i++ ) {
+        g_ptm_priv_data.itf[i].rx_desc = &p_rx_desc[i * dma_rx_descriptor_length];
+        g_ptm_priv_data.itf[i].tx_desc = &p_tx_desc[i * dma_tx_descriptor_length];
+        g_ptm_priv_data.itf[i].tx_skb = &ppskb[i * dma_tx_descriptor_length];
+    }
+
+    rx_desc.own     = 1;
+    rx_desc.c       = 0;
+    rx_desc.sop     = 1;
+    rx_desc.eop     = 1;
+    rx_desc.byteoff = RX_HEAD_MAC_ADDR_ALIGNMENT;
+    rx_desc.id      = 0;
+    rx_desc.err     = 0;
+    rx_desc.datalen = rx_max_packet_size;
+    for ( i = 0; i < MAX_ITF_NUMBER * dma_rx_descriptor_length; i++ ) {
+        skb = alloc_skb_rx();
+        if ( skb == NULL )
+            return -1;
+        rx_desc.dataptr = ((unsigned int)skb->data >> 2) & 0x0FFFFFFF;
+        p_rx_desc[i] = rx_desc;
+    }
+
+    return 0;
+}
+
+static INLINE void clear_priv_data(void)
+{
+    int i, j;
+    struct sk_buff *skb;
+
+    for ( i = 0; i < MAX_ITF_NUMBER; i++ ) {
+        if ( g_ptm_priv_data.itf[i].tx_skb != NULL ) {
+            for ( j = 0; j < dma_tx_descriptor_length; j++ )
+                if ( g_ptm_priv_data.itf[i].tx_skb[j] != NULL )
+                    dev_kfree_skb_any(g_ptm_priv_data.itf[i].tx_skb[j]);
+        }
+        if ( g_ptm_priv_data.itf[i].rx_desc != NULL ) {
+            for ( j = 0; j < dma_rx_descriptor_length; j++ ) {
+                if ( g_ptm_priv_data.itf[i].rx_desc[j].sop || g_ptm_priv_data.itf[i].rx_desc[j].eop ) {    //  descriptor initialized
+                    skb = get_skb_rx_pointer(g_ptm_priv_data.itf[i].rx_desc[j].dataptr);
+                    dev_kfree_skb_any(skb);
+                }
+            }
+        }
+    }
+
+    if ( g_ptm_priv_data.rx_desc_base != NULL )
+        kfree(g_ptm_priv_data.rx_desc_base);
+
+    if ( g_ptm_priv_data.tx_desc_base != NULL )
+        kfree(g_ptm_priv_data.tx_desc_base);
+
+    if ( g_ptm_priv_data.tx_skb_base != NULL )
+        kfree(g_ptm_priv_data.tx_skb_base);
+}
+
+static INLINE void init_tables(void)
+{
+    int i;
+    volatile unsigned int *p;
+    struct wrx_dma_channel_config rx_config = {0};
+    struct wtx_dma_channel_config tx_config = {0};
+    struct wrx_port_cfg_status    rx_port_cfg = { 0 };
+    struct wtx_port_cfg           tx_port_cfg = { 0 };
+
+    /*
+     *  CDM Block 1
+     */
+    IFX_REG_W32(CDM_CFG_RAM1_SET(0x00) | CDM_CFG_RAM0_SET(0x00), CDM_CFG);  //  CDM block 1 must be data memory and mapped to 0x5000 (dword addr)
+    p = CDM_DATA_MEMORY(0, 0);                                              //  Clear CDM block 1
+    for ( i = 0; i < CDM_DATA_MEMORY_DWLEN; i++, p++ )
+        IFX_REG_W32(0, p);
+
+    /*
+     *  General Registers
+     */
+    IFX_REG_W32(write_desc_delay, CFG_WAN_WRDES_DELAY);
+    IFX_REG_W32((1 << MAX_RX_DMA_CHANNEL_NUMBER) - 1, CFG_WRX_DMACH_ON);
+    IFX_REG_W32((1 << MAX_TX_DMA_CHANNEL_NUMBER) - 1, CFG_WTX_DMACH_ON);
+
+    IFX_REG_W32(8, CFG_WRX_LOOK_BITTH); // WAN RX EFM-TC Looking Threshold
+
+    IFX_REG_W32(eth_efmtc_crc_cfg, CFG_ETH_EFMTC_CRC);
+
+    /*
+     *  WRX DMA Channel Configuration Table
+     */
+    rx_config.deslen = dma_rx_descriptor_length;
+    rx_port_cfg.mfs = ETH_MAX_FRAME_LENGTH;
+    rx_port_cfg.local_state = 0;     // looking for sync
+    rx_port_cfg.partner_state = 0;   // parter receiver is out of sync
+
+    for ( i = 0; i < MAX_RX_DMA_CHANNEL_NUMBER; i++ ) {
+        rx_config.desba = ((unsigned int)g_ptm_priv_data.itf[i].rx_desc >> 2) & 0x0FFFFFFF;
+        *WRX_DMA_CHANNEL_CONFIG(i) = rx_config;
+
+        rx_port_cfg.dmach = i;
+        *WRX_PORT_CONFIG(i) = rx_port_cfg;
+    }
+
+    /*
+     *  WTX DMA Channel Configuration Table
+     */
+    tx_config.deslen = dma_tx_descriptor_length;
+    tx_port_cfg.tx_cwth1 = 5;
+    tx_port_cfg.tx_cwth2 = 4;
+
+    for ( i = 0; i < MAX_TX_DMA_CHANNEL_NUMBER; i++ ) {
+        tx_config.desba = ((unsigned int)g_ptm_priv_data.itf[i].tx_desc >> 2) & 0x0FFFFFFF;
+        *WTX_DMA_CHANNEL_CONFIG(i) = tx_config;
+
+        *WTX_PORT_CONFIG(i) = tx_port_cfg;
+    }
+}
+
+
+
+/*
+ * ####################################
+ *           Global Function
+ * ####################################
+ */
+
+static int ptm_showtime_enter(struct port_cell_info *port_cell, void *xdata_addr)
+{
+
+    g_showtime = 1;
+
+    printk("enter showtime\n");
+
+    return 0;
+}
+
+static int ptm_showtime_exit(void)
+{
+    if ( !g_showtime )
+        return -1;
+
+    g_showtime = 0;
+
+    printk("leave showtime\n");
+
+    return 0;
+}
+
+
+
+/*
+ * ####################################
+ *           Init/Cleanup API
+ * ####################################
+ */
+
+/*
+ *  Description:
+ *    Initialize global variables, PP32, comunication structures, register IRQ
+ *    and register device.
+ *  Input:
+ *    none
+ *  Output:
+ *    0    --- successful
+ *    else --- failure, usually it is negative value of error code
+ */
+static int ifx_ptm_init(void)
+{
+    int ret;
+    struct port_cell_info port_cell = {0};
+    void *xdata_addr = NULL;
+    int i;
+    char ver_str[256];
+
+    check_parameters();
+
+    ret = init_priv_data();
+    if ( ret != 0 ) {
+        err("INIT_PRIV_DATA_FAIL");
+        goto INIT_PRIV_DATA_FAIL;
+    }
+
+    ifx_ptm_init_chip();
+    init_tables();
+
+    for ( i = 0; i < ARRAY_SIZE(g_net_dev); i++ ) {
+        g_net_dev[i] = alloc_netdev(0, g_net_dev_name[i], ether_setup);
+        if ( g_net_dev[i] == NULL )
+            goto ALLOC_NETDEV_FAIL;
+        ptm_setup(g_net_dev[i], i);
+    }
+
+    for ( i = 0; i < ARRAY_SIZE(g_net_dev); i++ ) {
+        ret = register_netdev(g_net_dev[i]);
+        if ( ret != 0 )
+            goto REGISTER_NETDEV_FAIL;
+    }
+
+    /*  register interrupt handler  */
+    ret = request_irq(PPE_MAILBOX_IGU1_INT, mailbox_irq_handler, IRQF_DISABLED, "ptm_mailbox_isr", &g_ptm_priv_data);
+    if ( ret ) {
+        if ( ret == -EBUSY ) {
+            err("IRQ may be occupied by other driver, please reconfig to disable it.");
+        }
+        else {
+            err("request_irq fail");
+        }
+        goto REQUEST_IRQ_PPE_MAILBOX_IGU1_INT_FAIL;
+    }
+    disable_irq(PPE_MAILBOX_IGU1_INT);
+
+    ret = ifx_pp32_start(0);
+    if ( ret ) {
+        err("ifx_pp32_start fail!");
+        goto PP32_START_FAIL;
+    }
+    IFX_REG_W32(0, MBOX_IGU1_IER);
+    IFX_REG_W32(~0, MBOX_IGU1_ISRC);
+
+    enable_irq(PPE_MAILBOX_IGU1_INT);
+
+
+    proc_file_create();
+
+    port_cell.port_num = 1;
+    ifx_mei_atm_showtime_check(&g_showtime, &port_cell, &xdata_addr);
+
+    ifx_mei_atm_showtime_enter = ptm_showtime_enter;
+    ifx_mei_atm_showtime_exit  = ptm_showtime_exit;
+
+    ifx_ptm_version(ver_str);
+    printk(KERN_INFO "%s", ver_str);
+
+    printk("ifxmips_ptm: PTM init succeed\n");
+
+    return 0;
+
+PP32_START_FAIL:
+    free_irq(PPE_MAILBOX_IGU1_INT, &g_ptm_priv_data);
+REQUEST_IRQ_PPE_MAILBOX_IGU1_INT_FAIL:
+    i = ARRAY_SIZE(g_net_dev);
+REGISTER_NETDEV_FAIL:
+    while ( i-- )
+        unregister_netdev(g_net_dev[i]);
+    i = ARRAY_SIZE(g_net_dev);
+ALLOC_NETDEV_FAIL:
+    while ( i-- ) {
+        free_netdev(g_net_dev[i]);
+        g_net_dev[i] = NULL;
+    }
+INIT_PRIV_DATA_FAIL:
+    clear_priv_data();
+    printk("ifxmips_ptm: PTM init failed\n");
+    return ret;
+}
+
+/*
+ *  Description:
+ *    Release memory, free IRQ, and deregister device.
+ *  Input:
+ *    none
+ *  Output:
+ *   none
+ */
+static void __exit ifx_ptm_exit(void)
+{
+    int i;
+
+    ifx_mei_atm_showtime_enter = NULL;
+    ifx_mei_atm_showtime_exit  = NULL;
+
+    proc_file_delete();
+
+
+    ifx_pp32_stop(0);
+
+    free_irq(PPE_MAILBOX_IGU1_INT, &g_ptm_priv_data);
+
+    for ( i = 0; i < ARRAY_SIZE(g_net_dev); i++ )
+        unregister_netdev(g_net_dev[i]);
+
+    for ( i = 0; i < ARRAY_SIZE(g_net_dev); i++ ) {
+        free_netdev(g_net_dev[i]);
+        g_net_dev[i] = NULL;
+    }
+
+    ifx_ptm_uninit_chip();
+
+    clear_priv_data();
+}
+
+module_init(ifx_ptm_init);
+module_exit(ifx_ptm_exit);