/*
 * Copyright(c) 2007 Atheros Corporation. All rights reserved.
 *
 * Derived from Intel e1000 driver
 * Copyright(c) 1999 - 2005 Intel Corporation. All rights reserved.
 *
 * 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.
 *
 * 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.
 *
 * There are a lot of defines in here that are unused and/or have cryptic
 * names.  Please leave them alone, as they're the closest thing we have
 * to a spec from Atheros at present. *ahem* -- CHS
 */

/* ethtool support for at */

#include <linux/netdevice.h>

#ifdef SIOCETHTOOL
#include <linux/ethtool.h>

#include "at.h"

#ifdef ETHTOOL_OPS_COMPAT
#include "kcompat_ethtool.c"
#endif

extern char at_driver_name[];
extern char at_driver_version[];

extern int at_up(struct at_adapter *adapter);
extern void at_down(struct at_adapter *adapter);
extern void at_reinit_locked(struct at_adapter *adapter);
extern s32 at_reset_hw(struct at_hw *hw);

static int
at_get_settings(struct net_device *netdev, struct ethtool_cmd *ecmd)
{
    struct at_adapter *adapter = netdev_priv(netdev);
    struct at_hw *hw = &adapter->hw;

    ecmd->supported = (SUPPORTED_10baseT_Half |
                       SUPPORTED_10baseT_Full |
                       SUPPORTED_100baseT_Half |
                       SUPPORTED_100baseT_Full |
                       SUPPORTED_Autoneg |
                       SUPPORTED_TP);
    if (hw->nic_type == athr_l1e) {
        ecmd->supported |= SUPPORTED_1000baseT_Full;
    }
    ecmd->advertising = ADVERTISED_TP;

    ecmd->advertising |= ADVERTISED_Autoneg;
    ecmd->advertising |= hw->autoneg_advertised;
    
    ecmd->port = PORT_TP;
    ecmd->phy_address = 0;
    ecmd->transceiver = XCVR_INTERNAL;

    if (adapter->link_speed != SPEED_0) {
        ecmd->speed = adapter->link_speed;
        if (adapter->link_duplex == FULL_DUPLEX)
            ecmd->duplex = DUPLEX_FULL;
        else
            ecmd->duplex = DUPLEX_HALF;
    } else {
        ecmd->speed = -1;
        ecmd->duplex = -1;
    }

    ecmd->autoneg = AUTONEG_ENABLE;
    return 0;
}

static int
at_set_settings(struct net_device *netdev, struct ethtool_cmd *ecmd)
{
    struct at_adapter *adapter = netdev_priv(netdev);
    struct at_hw *hw = &adapter->hw;

    while (test_and_set_bit(__AT_RESETTING, &adapter->flags))
        msleep(1);

    if (ecmd->autoneg == AUTONEG_ENABLE) {
        u16 adv4, adv9;
           

#define MY_ADV_MASK (ADVERTISE_10_HALF| \
                     ADVERTISE_10_FULL| \
                     ADVERTISE_100_HALF| \
                     ADVERTISE_100_FULL |\
                     ADVERTISE_1000_FULL)


//printk("advertising=%x\n", ecmd->advertising);

        if ((ecmd->advertising&ADVERTISE_1000_FULL)) {
            if (hw->nic_type == athr_l1e) {
                hw->autoneg_advertised =  ecmd->advertising&MY_ADV_MASK;
            } else {
                clear_bit(__AT_RESETTING, &adapter->flags);
                return -EINVAL;
            }
        } else if (ecmd->advertising&ADVERTISE_1000_HALF) {
                clear_bit(__AT_RESETTING, &adapter->flags);
                return -EINVAL;
        } else {
            hw->autoneg_advertised = ecmd->advertising&MY_ADV_MASK;
        } 
        ecmd->advertising = hw->autoneg_advertised 
                        | ADVERTISED_TP | ADVERTISED_Autoneg;
         
        adv4 = hw->mii_autoneg_adv_reg & ~MII_AR_SPEED_MASK;
        adv9 = hw->mii_1000t_ctrl_reg & ~MII_AT001_CR_1000T_SPEED_MASK;
        if (hw->autoneg_advertised&ADVERTISE_10_HALF) {
            adv4 |= MII_AR_10T_HD_CAPS;
        }
        if (hw->autoneg_advertised&ADVERTISE_10_FULL) {
            adv4 |= MII_AR_10T_FD_CAPS;
        }
        if (hw->autoneg_advertised&ADVERTISE_100_HALF) {
            adv4 |= MII_AR_100TX_HD_CAPS;
        }
        if (hw->autoneg_advertised&ADVERTISE_100_FULL) {
            adv4 |= MII_AR_100TX_FD_CAPS;
        }
        if (hw->autoneg_advertised&ADVERTISE_1000_FULL) {
            adv9 |= MII_AT001_CR_1000T_FD_CAPS; 
        }

        if (adv4 != hw->mii_autoneg_adv_reg ||
            adv9 != hw->mii_1000t_ctrl_reg) {
            hw->mii_autoneg_adv_reg = adv4;
            hw->mii_1000t_ctrl_reg = adv9;
            hw->re_autoneg = TRUE;
        }

    } else {
        clear_bit(__AT_RESETTING, &adapter->flags);
        return -EINVAL;
    }

    /* reset the link */

    if (netif_running(adapter->netdev)) {
        at_down(adapter);
        at_up(adapter);
    } else
        at_reset_hw(&adapter->hw);

    clear_bit(__AT_RESETTING, &adapter->flags);
    return 0;
}

static u32
at_get_tx_csum(struct net_device *netdev)
{
    return (netdev->features & NETIF_F_HW_CSUM) != 0;
}

static u32
at_get_msglevel(struct net_device *netdev)
{
#if DBG
    return 1;
#else
    return 0;
#endif
}   


static void
at_set_msglevel(struct net_device *netdev, u32 data)
{
}

static int
at_get_regs_len(struct net_device *netdev)
{
#define AT_REGS_LEN 75
    return AT_REGS_LEN * sizeof(u32);
}

static void
at_get_regs(struct net_device *netdev,
           struct ethtool_regs *regs, void *p)
{
    struct at_adapter *adapter = netdev_priv(netdev);
    struct at_hw *hw = &adapter->hw;
    u32 *regs_buff = p;
    u16 phy_data;

    memset(p, 0, AT_REGS_LEN * sizeof(u32));

    regs->version = (1 << 24) | (hw->revision_id << 16) | hw->device_id;

    regs_buff[0]  = AT_READ_REG(hw, REG_VPD_CAP);
    regs_buff[1]  = AT_READ_REG(hw, REG_SPI_FLASH_CTRL);
    regs_buff[2]  = AT_READ_REG(hw, REG_SPI_FLASH_CONFIG);
    regs_buff[3]  = AT_READ_REG(hw, REG_TWSI_CTRL);
    regs_buff[4]  = AT_READ_REG(hw, REG_PCIE_DEV_MISC_CTRL);
    regs_buff[5]  = AT_READ_REG(hw, REG_MASTER_CTRL);
    regs_buff[6]  = AT_READ_REG(hw, REG_MANUAL_TIMER_INIT);
    regs_buff[7]  = AT_READ_REG(hw, REG_IRQ_MODU_TIMER_INIT);
    regs_buff[8]  = AT_READ_REG(hw, REG_GPHY_CTRL);
    regs_buff[9]  = AT_READ_REG(hw, REG_CMBDISDMA_TIMER);
    regs_buff[10] = AT_READ_REG(hw, REG_IDLE_STATUS);
    regs_buff[11] = AT_READ_REG(hw, REG_MDIO_CTRL);
    regs_buff[12] = AT_READ_REG(hw, REG_SERDES_LOCK);
    regs_buff[13] = AT_READ_REG(hw, REG_MAC_CTRL);
    regs_buff[14] = AT_READ_REG(hw, REG_MAC_IPG_IFG);
    regs_buff[15] = AT_READ_REG(hw, REG_MAC_STA_ADDR);
    regs_buff[16] = AT_READ_REG(hw, REG_MAC_STA_ADDR+4);
    regs_buff[17] = AT_READ_REG(hw, REG_RX_HASH_TABLE);
    regs_buff[18] = AT_READ_REG(hw, REG_RX_HASH_TABLE+4);
    regs_buff[19] = AT_READ_REG(hw, REG_MAC_HALF_DUPLX_CTRL);
    regs_buff[20] = AT_READ_REG(hw, REG_MTU);
    regs_buff[21] = AT_READ_REG(hw, REG_WOL_CTRL);
    regs_buff[22] = AT_READ_REG(hw, REG_SRAM_TRD_ADDR);
    regs_buff[23] = AT_READ_REG(hw, REG_SRAM_TRD_LEN);
    regs_buff[24] = AT_READ_REG(hw, REG_SRAM_RXF_ADDR);
    regs_buff[25] = AT_READ_REG(hw, REG_SRAM_RXF_LEN);
    regs_buff[26] = AT_READ_REG(hw, REG_SRAM_TXF_ADDR);
    regs_buff[27] = AT_READ_REG(hw, REG_SRAM_TXF_LEN);
    regs_buff[28] = AT_READ_REG(hw, REG_SRAM_TCPH_ADDR);
    regs_buff[29] = AT_READ_REG(hw, REG_SRAM_PKTH_ADDR);
/*
    // description address
    regs_buff[30] = AT_READ_REG(hw, REG_DESC_BASE_ADDR_HI);
    regs_buff[31] = AT_READ_REG(hw, REG_TPD_BASE_ADDR_LO);
    regs_buff[32] = AT_READ_REG(hw, REG_TPD_RING_SIZE);
    regs_buff[33] = AT_READ_REG(hw, REG_HOST_RXF_HEAD);
    regs_buff[34] = AT_READ_REG(hw, REG_HOST_RXF_TAIL);
    regs_buff[35] = AT_READ_REG(hw, REG_HOST_RXRAM_SIZE);
    regs_buff[36] = AT_READ_REG(hw, REG_HOST_RXF1_HEAD);
    regs_buff[37] = AT_READ_REG(hw, REG_HOST_RXF1_TAIL);
    regs_buff[38] = AT_READ_REG(hw, REG_HOST_RXF2_HEAD);
    regs_buff[39] = AT_READ_REG(hw, REG_HOST_RXF2_TAIL);
    regs_buff[40] = AT_READ_REG(hw, REG_HOST_RXF3_HEAD);
    regs_buff[41] = AT_READ_REG(hw, REG_HOST_RXF3_TAIL);
    // mail box
    regs_buff[42] = AT_READ_REG(hw, REG_HOST_RXF0_WADDR);
    regs_buff[43] = AT_READ_REG(hw, REG_HOST_RXF1_WADDR);
    regs_buff[44] = AT_READ_REG(hw, REG_HOST_RXF2_WADDR);
    regs_buff[45] = AT_READ_REG(hw, REG_HOST_RXF3_WADDR);
    regs_buff[46] = AT_READ_REG(hw, REG_TPD_CONS_IDX);
    regs_buff[47] = AT_READ_REG(hw, REG_MB_RXF0_RADDR);
    regs_buff[48] = AT_READ_REG(hw, REG_MB_RXF1_RADDR);
    regs_buff[49] = AT_READ_REG(hw, REG_MB_RXF2_RADDR);
    regs_buff[50] = AT_READ_REG(hw, REG_MB_RXF3_RADDR);
    regs_buff[51] = AT_READ_REG(hw, REG_MB_TPD_PROD_IDX);
    // RSS
    regs_buff[52] = AT_READ_REG(hw, REG_RSS_KEY0);
    regs_buff[53] = AT_READ_REG(hw, REG_RSS_KEY1);
    regs_buff[54] = AT_READ_REG(hw, REG_RSS_KEY2);
    regs_buff[55] = AT_READ_REG(hw, REG_RSS_KEY3);
    regs_buff[56] = AT_READ_REG(hw, REG_RSS_HASH_VALUE);
    regs_buff[57] = AT_READ_REG(hw, REG_RSS_HASH_FLAG);
    regs_buff[58] = AT_READ_REG(hw, REG_IDT_TABLE);
    regs_buff[59] = AT_READ_REG(hw, REG_BASE_CPU_NUMBER);
    // TXQ
    regs_buff[60] = AT_READ_REG(hw, REG_TXQ_CTRL);
    regs_buff[61] = AT_READ_REG(hw, REG_TX_JUMBO_TASK_TH);
    // RXQ
    regs_buff[62] = AT_READ_REG(hw, REG_RXQ_CTRL);
    regs_buff[63] = AT_READ_REG(hw, REG_RXQ_JMBOSZ_RRDTIM);
    regs_buff[64] = AT_READ_REG(hw, REG_RXQ_RXF_PAUSE_THRESH);
    // DMA 
    regs_buff[65] = AT_READ_REG(hw, REG_DMA_CTRL);
    // misc
    regs_buff[66] = AT_READ_REG(hw, REG_SMB_STAT_TIMER);
    regs_buff[67] = AT_READ_REGW(hw, REG_TRIG_RRD_THRESH);
    regs_buff[68] = AT_READ_REGW(hw, REG_TRIG_TPD_THRESH);
    regs_buff[69] = AT_READ_REGW(hw, REG_TRIG_RXTIMER);
    regs_buff[70] = AT_READ_REGW(hw, REG_TRIG_TXTIMER);
    regs_buff[71] = AT_READ_REG(hw, REG_ISR);
    regs_buff[72] = AT_READ_REG(hw, REG_IMR);
*/  
    at_read_phy_reg(hw, MII_BMCR, &phy_data);
    regs_buff[73] = (u32)phy_data;
    at_read_phy_reg(hw, MII_BMSR, &phy_data);
    regs_buff[74] = (u32)phy_data;
}

static int
at_get_eeprom_len(struct net_device *netdev)
{
    struct at_adapter *adapter = netdev_priv(netdev);
    
    if (!check_eeprom_exist(&adapter->hw)) {
        return 512;
    } else 
        return 0;
}

static int
at_get_eeprom(struct net_device *netdev,
                      struct ethtool_eeprom *eeprom, u8 *bytes)
{
    struct at_adapter *adapter = netdev_priv(netdev);
    struct at_hw *hw = &adapter->hw;
    u32 *eeprom_buff;
    int first_dword, last_dword;
    int ret_val = 0;
    int i;

    if (eeprom->len == 0)
        return -EINVAL;

    if (check_eeprom_exist(hw)) {
        return -EINVAL;
    }
        
    eeprom->magic = hw->vendor_id | (hw->device_id << 16);

    first_dword = eeprom->offset >> 2;
    last_dword = (eeprom->offset + eeprom->len - 1) >> 2;

    eeprom_buff = kmalloc(sizeof(u32) *
            (last_dword - first_dword + 1), GFP_KERNEL);
    if (!eeprom_buff)
        return -ENOMEM;

    for (i=first_dword; i < last_dword; i++) {
        if (!read_eeprom(hw, i*4, &(eeprom_buff[i-first_dword])))
            return -EIO;
    }

    memcpy(bytes, (u8 *)eeprom_buff + (eeprom->offset & 3),
            eeprom->len);
    kfree(eeprom_buff);

    return ret_val;
}

static int
at_set_eeprom(struct net_device *netdev,
                      struct ethtool_eeprom *eeprom, u8 *bytes)
{
    struct at_adapter *adapter = netdev_priv(netdev);
    struct at_hw *hw = &adapter->hw;
    u32 *eeprom_buff;
    u32 *ptr;
    int max_len, first_dword, last_dword, ret_val = 0;
    int i;

    if (eeprom->len == 0)
        return -EOPNOTSUPP;

    if (eeprom->magic != (hw->vendor_id | (hw->device_id << 16)))
        return -EFAULT;

    max_len = 512;

    first_dword = eeprom->offset >> 2;
    last_dword = (eeprom->offset + eeprom->len - 1) >> 2;
    eeprom_buff = kmalloc(max_len, GFP_KERNEL);
    if (!eeprom_buff)
        return -ENOMEM;

    ptr = (u32 *)eeprom_buff;

    if (eeprom->offset & 3) {
        /* need read/modify/write of first changed EEPROM word */
        /* only the second byte of the word is being modified */
        if (!read_eeprom(hw, first_dword*4, &(eeprom_buff[0])))
            return -EIO;
        ptr++;
    }
    if (((eeprom->offset + eeprom->len) & 3) ) {
        /* need read/modify/write of last changed EEPROM word */
        /* only the first byte of the word is being modified */
        
        if (!read_eeprom(hw, last_dword*4, &(eeprom_buff[last_dword - first_dword])))
            return -EIO;
    }

    /* Device's eeprom is always little-endian, word addressable */
    memcpy(ptr, bytes, eeprom->len);

    for (i = 0; i < last_dword - first_dword + 1; i++) {
        if (!write_eeprom(hw, ((first_dword+i)*4), eeprom_buff[i]))
            return -EIO;
    }

    kfree(eeprom_buff);
    return ret_val;
}

static void
at_get_drvinfo(struct net_device *netdev,
                       struct ethtool_drvinfo *drvinfo)
{
    struct at_adapter *adapter = netdev_priv(netdev);

    strncpy(drvinfo->driver,  at_driver_name, 32);
    strncpy(drvinfo->version, at_driver_version, 32);
    strncpy(drvinfo->fw_version, "L1e", 32);
    strncpy(drvinfo->bus_info, pci_name(adapter->pdev), 32);
    drvinfo->n_stats = 0;
    drvinfo->testinfo_len = 0;
    drvinfo->regdump_len = at_get_regs_len(netdev);
    drvinfo->eedump_len = at_get_eeprom_len(netdev);
}

static void
at_get_wol(struct net_device *netdev, struct ethtool_wolinfo *wol)
{
    struct at_adapter *adapter = netdev_priv(netdev);

    wol->supported = WAKE_MAGIC|WAKE_PHY;
    wol->wolopts = 0;

    if (adapter->wol & AT_WUFC_EX)
        wol->wolopts |= WAKE_UCAST;
    if (adapter->wol & AT_WUFC_MC)
        wol->wolopts |= WAKE_MCAST;
    if (adapter->wol & AT_WUFC_BC)
        wol->wolopts |= WAKE_BCAST;
    if (adapter->wol & AT_WUFC_MAG)
        wol->wolopts |= WAKE_MAGIC;
    if (adapter->wol & AT_WUFC_LNKC)
        wol->wolopts |= WAKE_PHY;

    return;
}

static int
at_set_wol(struct net_device *netdev, struct ethtool_wolinfo *wol)
{
    struct at_adapter *adapter = netdev_priv(netdev);

    if (wol->wolopts & (WAKE_ARP | WAKE_MAGICSECURE))
        return -EOPNOTSUPP;

    if (wol->wolopts & (WAKE_MCAST|WAKE_BCAST|WAKE_MCAST)) {
        AT_DBG("Interface does not support broadcast/multicast frame wake-up packets\n");
        return -EOPNOTSUPP;
    }

    /* these settings will always override what we currently have */
    adapter->wol = 0;

    if (wol->wolopts & WAKE_MAGIC) {
        adapter->wol |= AT_WUFC_MAG;
        DEBUGOUT("magic WOL enable");
    }
    if (wol->wolopts & WAKE_PHY) {
        adapter->wol |= AT_WUFC_LNKC;
        DEBUGOUT("linkchg WOL enable");
    }

    return 0;
}

static int
at_nway_reset(struct net_device *netdev)
{
    struct at_adapter *adapter = netdev_priv(netdev);
    if (netif_running(netdev))
        at_reinit_locked(adapter);
    return 0;
}


static struct ethtool_ops at_ethtool_ops = {
    .get_settings           = at_get_settings,
    .set_settings           = at_set_settings,
    .get_drvinfo            = at_get_drvinfo,
    .get_regs_len           = at_get_regs_len,
    .get_regs               = at_get_regs,
    .get_wol                = at_get_wol,
    .set_wol                = at_set_wol,
    .get_msglevel           = at_get_msglevel,
    .set_msglevel           = at_set_msglevel,
    .nway_reset             = at_nway_reset,
    .get_link               = ethtool_op_get_link,
    .get_eeprom_len         = at_get_eeprom_len,
    .get_eeprom             = at_get_eeprom,
    .set_eeprom             = at_set_eeprom,
    .get_tx_csum            = at_get_tx_csum,
    .get_sg                 = ethtool_op_get_sg,
    .set_sg                 = ethtool_op_set_sg,
#ifdef NETIF_F_TSO
    .get_tso                = ethtool_op_get_tso,
#endif
#ifdef ETHTOOL_GPERMADDR
    .get_perm_addr          = ethtool_op_get_perm_addr,
#endif
};

void at_set_ethtool_ops(struct net_device *netdev)
{
    SET_ETHTOOL_OPS(netdev, &at_ethtool_ops);
}
#endif  /* SIOCETHTOOL */

