root/usr/src/uts/common/io/chxge/com/ch_mac.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (C) 2003-2005 Chelsio Communications.  All rights reserved.
 */

#include "gmac.h"
#include "regs.h"
#include "fpga_defs.h"

#define MAC_CSR_INTERFACE_GMII  0x0
#define MAC_CSR_INTERFACE_TBI   0x1
#define MAC_CSR_INTERFACE_MII   0x2
#define MAC_CSR_INTERFACE_RMII  0x3

/* Chelsio's MAC statistics. */
struct mac_statistics {

        /* Transmit */
        u32 TxFramesTransmittedOK;
        u32 TxReserved1;
        u32 TxReserved2;
        u32 TxOctetsTransmittedOK;
        u32 TxFramesWithDeferredXmissions;
        u32 TxLateCollisions;
        u32 TxFramesAbortedDueToXSCollisions;
        u32 TxFramesLostDueToIntMACXmitError;
        u32 TxReserved3;
        u32 TxMulticastFrameXmittedOK;
        u32 TxBroadcastFramesXmittedOK;
        u32 TxFramesWithExcessiveDeferral;
        u32 TxPAUSEMACCtrlFramesTransmitted;

        /* Receive */
        u32 RxFramesReceivedOK;
        u32 RxFrameCheckSequenceErrors;
        u32 RxAlignmentErrors;
        u32 RxOctetsReceivedOK;
        u32 RxFramesLostDueToIntMACRcvError;
        u32 RxMulticastFramesReceivedOK;
        u32 RxBroadcastFramesReceivedOK;
        u32 RxInRangeLengthErrors;
        u32 RxTxOutOfRangeLengthField;
        u32 RxFrameTooLongErrors;
        u32 RxPAUSEMACCtrlFramesReceived;
};

static int static_aPorts[] = {
        FPGA_GMAC_INTERRUPT_PORT0,
        FPGA_GMAC_INTERRUPT_PORT1,
        FPGA_GMAC_INTERRUPT_PORT2,
        FPGA_GMAC_INTERRUPT_PORT3
};

struct _cmac_instance {
        u32 index;
};

static int mac_intr_enable(struct cmac *mac)
{
        u32 mac_intr;

        if (t1_is_asic(mac->adapter)) {
                /* ASIC */
                /*EMPTY*/
                /* We don't use the on chip MAC for ASIC products. */
        } else {
                /* FPGA */

                /* Set parent gmac interrupt. */
                mac_intr = t1_read_reg_4(mac->adapter, A_PL_ENABLE);
                mac_intr |= FPGA_PCIX_INTERRUPT_GMAC;
                t1_write_reg_4(mac->adapter, A_PL_ENABLE, mac_intr);

                mac_intr = t1_read_reg_4(mac->adapter,
                        FPGA_GMAC_ADDR_INTERRUPT_ENABLE);
                mac_intr |= static_aPorts[mac->instance->index];
                t1_write_reg_4(mac->adapter,
                        FPGA_GMAC_ADDR_INTERRUPT_ENABLE, mac_intr);
        }

        return (0);
}

static int mac_intr_disable(struct cmac *mac)
{
        u32 mac_intr;

        if (t1_is_asic(mac->adapter)) {
                /* ASIC */
                /*EMPTY*/
                /* We don't use the on chip MAC for ASIC products. */
        } else {
                /* FPGA */

                /* Set parent gmac interrupt. */
                mac_intr = t1_read_reg_4(mac->adapter, A_PL_ENABLE);
                mac_intr &= ~FPGA_PCIX_INTERRUPT_GMAC;
                t1_write_reg_4(mac->adapter, A_PL_ENABLE, mac_intr);

                mac_intr = t1_read_reg_4(mac->adapter,
                        FPGA_GMAC_ADDR_INTERRUPT_ENABLE);
                mac_intr &= ~(static_aPorts[mac->instance->index]);
                t1_write_reg_4(mac->adapter,
                        FPGA_GMAC_ADDR_INTERRUPT_ENABLE, mac_intr);
        }

        return (0);
}

static int mac_intr_clear(struct cmac *mac)
{
        u32 mac_intr;

        if (t1_is_asic(mac->adapter)) {
                /* ASIC */
                /*EMPTY*/
                /* We don't use the on chip MAC for ASIC products. */
        } else {
                /* FPGA */

                /* Set parent gmac interrupt. */
                t1_write_reg_4(mac->adapter, A_PL_CAUSE,
                        FPGA_PCIX_INTERRUPT_GMAC);

                mac_intr = t1_read_reg_4(mac->adapter,
                        FPGA_GMAC_ADDR_INTERRUPT_CAUSE);
                mac_intr |= (static_aPorts[mac->instance->index]);
                t1_write_reg_4(mac->adapter,
                        FPGA_GMAC_ADDR_INTERRUPT_CAUSE, mac_intr);
        }

        return (0);
}

static int mac_get_address(struct cmac *mac, u8 addr[6])
{
        u32 data32_lo, data32_hi;

        data32_lo = t1_read_reg_4(mac->adapter,
                        MAC_REG_IDLO(mac->instance->index));
        data32_hi = t1_read_reg_4(mac->adapter,
                        MAC_REG_IDHI(mac->instance->index));

        addr[0] = (u8) ((data32_hi >> 8) & 0xFF);
        addr[1] = (u8) ((data32_hi) & 0xFF);
        addr[2] = (u8) ((data32_lo >> 24) & 0xFF);
        addr[3] = (u8) ((data32_lo >> 16) & 0xFF);
        addr[4] = (u8) ((data32_lo >> 8) & 0xFF);
        addr[5] = (u8) ((data32_lo) & 0xFF);
        return (0);
}

static int mac_reset(struct cmac *mac)
{
        u32 data32;
        int mac_in_reset, time_out = 100;
        int idx = mac->instance->index;

        data32 = t1_read_reg_4(mac->adapter, MAC_REG_CSR(idx));
        t1_write_reg_4(mac->adapter, MAC_REG_CSR(idx),
                data32 | F_MAC_RESET);

        do {
                data32 = t1_read_reg_4(mac->adapter,
                        MAC_REG_CSR(idx));
                mac_in_reset = data32 & F_MAC_RESET;
                if (mac_in_reset)
                        DELAY_US(1);
        } while (mac_in_reset && --time_out);

        if (mac_in_reset) {
                CH_ERR("%s: MAC %d reset timed out\n",
                        adapter_name(mac->adapter), idx);
                return (2);
        }

        return (0);
}

static int mac_set_rx_mode(struct cmac *mac, struct t1_rx_mode *rm)
{
        u32 val;

        val = t1_read_reg_4(mac->adapter,
                            MAC_REG_CSR(mac->instance->index));
        val &= ~(F_MAC_PROMISC | F_MAC_MC_ENABLE);
        val |= V_MAC_PROMISC(t1_rx_mode_promisc(rm) != 0);
        val |= V_MAC_MC_ENABLE(t1_rx_mode_allmulti(rm) != 0);
        t1_write_reg_4(mac->adapter,
                MAC_REG_CSR(mac->instance->index), val);

        return (0);
}

static int mac_set_speed_duplex_fc(struct cmac *mac, int speed, int duplex,
        int fc)
{
        u32 data32;

        data32 = t1_read_reg_4(mac->adapter,
                MAC_REG_CSR(mac->instance->index));
        data32 &= ~(F_MAC_HALF_DUPLEX | V_MAC_SPEED(M_MAC_SPEED) |
                V_INTERFACE(M_INTERFACE) | F_MAC_TX_PAUSE_ENABLE |
                F_MAC_RX_PAUSE_ENABLE);

        switch (speed) {
        case SPEED_10:
        case SPEED_100:
                data32 |= V_INTERFACE(MAC_CSR_INTERFACE_MII);
                data32 |= V_MAC_SPEED(speed == SPEED_10 ? 0 : 1);
                break;
        case SPEED_1000:
                data32 |= V_INTERFACE(MAC_CSR_INTERFACE_GMII);
                data32 |= V_MAC_SPEED(2);
                break;
        }

        if (duplex >= 0)
                data32 |= V_MAC_HALF_DUPLEX(duplex == DUPLEX_HALF);

        if (fc >= 0) {
                data32 |= V_MAC_RX_PAUSE_ENABLE((fc & PAUSE_RX) != 0);
                data32 |= V_MAC_TX_PAUSE_ENABLE((fc & PAUSE_TX) != 0);
        }

        t1_write_reg_4(mac->adapter,
                MAC_REG_CSR(mac->instance->index), data32);
        return (0);
}

static int mac_enable(struct cmac *mac, int which)
{
        u32 val;

        val = t1_read_reg_4(mac->adapter,
                            MAC_REG_CSR(mac->instance->index));
        if (which & MAC_DIRECTION_RX)
                val |= F_MAC_RX_ENABLE;
        if (which & MAC_DIRECTION_TX)
                val |= F_MAC_TX_ENABLE;
        t1_write_reg_4(mac->adapter,
                MAC_REG_CSR(mac->instance->index), val);
        return (0);
}

static int mac_disable(struct cmac *mac, int which)
{
        u32 val;

        val = t1_read_reg_4(mac->adapter,
                MAC_REG_CSR(mac->instance->index));
        if (which & MAC_DIRECTION_RX)
                val &= ~F_MAC_RX_ENABLE;
        if (which & MAC_DIRECTION_TX)
                val &= ~F_MAC_TX_ENABLE;
        t1_write_reg_4(mac->adapter,
                MAC_REG_CSR(mac->instance->index), val);
        return (0);
}

int
mac_set_ifs(struct cmac *mac, u32 mode)
{
        t1_write_reg_4(mac->adapter,
                MAC_REG_IFS(mac->instance->index), mode);

        return (0);
}

int
mac_enable_isl(struct cmac *mac)
{
        u32 data32 = t1_read_reg_4(mac->adapter,
                MAC_REG_CSR(mac->instance->index));
        data32 |= F_MAC_RX_ENABLE | F_MAC_TX_ENABLE;
        t1_write_reg_4(mac->adapter,
                MAC_REG_CSR(mac->instance->index), data32);

        return (0);
}

static int mac_set_mtu(struct cmac *mac, int mtu)
{
        if (mtu > 9600)
                return (-EINVAL);
        t1_write_reg_4(mac->adapter,
                MAC_REG_LARGEFRAMELENGTH(mac->instance->index),
                mtu + 14 + 4);
        return (0);
}

/* ARGSUSED */
static const struct cmac_statistics *mac_update_statistics(struct cmac *mac,
        int flag)
{
        struct mac_statistics st;
        u32 *p = (u32 *) & st, i;

        t1_write_reg_4(mac->adapter,
                MAC_REG_RMCNT(mac->instance->index), 0);
        for (i = 0; i < sizeof (st) / sizeof (u32); i++)
                *p++ = t1_read_reg_4(mac->adapter,
                        MAC_REG_RMDATA(mac->instance->index));

        /* XXX convert stats */
        return (&mac->stats);
}

static void mac_destroy(struct cmac *mac)
{
        t1_os_free((void *)mac, sizeof (*mac) + sizeof (cmac_instance));
}

#ifdef C99_NOT_SUPPORTED
static struct cmac_ops chelsio_mac_ops = {
        mac_destroy,
        mac_reset,
        mac_intr_enable,
        mac_intr_disable,
        mac_intr_clear,
        NULL,
        mac_enable,
        mac_disable,
        NULL,
        NULL,
        mac_set_mtu,
        mac_set_rx_mode,
        mac_set_speed_duplex_fc,
        NULL,
        mac_update_statistics,
        mac_get_address,
        NULL
};
#else
static struct cmac_ops chelsio_mac_ops = {
        .destroy                = mac_destroy,
        .reset                  = mac_reset,
        .interrupt_enable       = mac_intr_enable,
        .interrupt_disable      = mac_intr_disable,
        .interrupt_clear        = mac_intr_clear,
        .enable                 = mac_enable,
        .disable                = mac_disable,
        .set_mtu                = mac_set_mtu,
        .set_rx_mode            = mac_set_rx_mode,
        .set_speed_duplex_fc    = mac_set_speed_duplex_fc,
        .macaddress_get         = mac_get_address,
        .statistics_update      = mac_update_statistics,
};
#endif

static struct cmac *mac_create(adapter_t *adapter, int index)
{
        struct cmac *mac;
        u32 data32;

        if (index >= 4)
                return (NULL);

        mac = t1_os_malloc_wait_zero(sizeof (*mac) + sizeof (cmac_instance));
        if (!mac)
                return (NULL);

        mac->ops = &chelsio_mac_ops;
        mac->instance = (cmac_instance *) (mac + 1);

        mac->instance->index = index;
        mac->adapter = adapter;

        data32 = t1_read_reg_4(adapter, MAC_REG_CSR(mac->instance->index));
        data32 &= ~(F_MAC_RESET | F_MAC_PROMISC | F_MAC_PROMISC |
                    F_MAC_LB_ENABLE | F_MAC_RX_ENABLE | F_MAC_TX_ENABLE);
        data32 |= F_MAC_JUMBO_ENABLE;
        t1_write_reg_4(adapter, MAC_REG_CSR(mac->instance->index), data32);

        /* Initialize the random backoff seed. */
        data32 = 0x55aa + (3 * index);
        t1_write_reg_4(adapter,
                MAC_REG_GMRANDBACKOFFSEED(mac->instance->index), data32);

        /* Check to see if the mac address needs to be set manually. */
        data32 = t1_read_reg_4(adapter, MAC_REG_IDLO(mac->instance->index));
        if (data32 == 0 || data32 == 0xffffffff) {
                /*
                 * Add a default MAC address if we can't read one.
                 */
                t1_write_reg_4(adapter, MAC_REG_IDLO(mac->instance->index),
                        0x43FFFFFF - index);
                t1_write_reg_4(adapter, MAC_REG_IDHI(mac->instance->index),
                        0x0007);
        }

        (void) mac_set_mtu(mac, 1500);
        return (mac);
}

struct gmac t1_chelsio_mac_ops = {
        0,
        mac_create
};