root/usr/src/uts/common/io/chxge/com/mc4.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
 */

/*
 * This file is part of the Chelsio T1 Ethernet driver.
 *
 * Copyright (C) 2003-2005 Chelsio Communications.  All rights reserved.
 */

#include "common.h"
#include "regs.h"
#include "mc4.h"

struct pemc4 {
        adapter_t *adapter;
        unsigned int size;
        unsigned int nwords;           /* MC4 width in terms of 32-bit words */
        struct pemc4_intr_counts intr_cnt;
};

void t1_mc4_destroy(struct pemc4 *mc4)
{
        t1_os_free((void *)mc4, sizeof(*mc4));
}

#define is_MC4A(adapter) (!t1_is_T1B(adapter))

/* Calculate amount of MC4 memory. */
static unsigned int __devinit mc4_calc_size(adapter_t *adapter)
{
        u32 mc4_cfg = t1_read_reg_4(adapter, A_MC4_CFG);
        unsigned int width = is_MC4A(adapter) ? G_MC4A_WIDTH(mc4_cfg) :
                                                !!(mc4_cfg & F_MC4_NARROW);

        return (256 * 1024 * 1024) >> width;
}

/*
 * Write a value to a register and check that the write completed.  These
 * writes normally complete in a cycle or two, so one read should suffice but
 * just in case we give them a bit of grace period.  Note that the very first
 * read exists to flush the posted write to the device.
 */
static int wrreg_wait(adapter_t *adapter, unsigned int addr, u32 val)
{
        int attempts = 2;

        t1_write_reg_4(adapter, addr, val);
        val = t1_read_reg_4(adapter, addr);                   /* flush */
        while (attempts--) {
                if (!(t1_read_reg_4(adapter, addr) & F_BUSY))
                        return 0;
                if (attempts)
                        DELAY_US(1);
        }
        CH_ERR("%s: write to MC4 register 0x%x timed out\n",
               adapter_name(adapter), addr);
        return -EIO;
}

#define MC4_DLL_DONE (F_MASTER_DLL_LOCKED | F_MASTER_DLL_MAX_TAP_COUNT)

int t1_mc4_init(struct pemc4 *mc4, unsigned int mc4_clock)
{
        int attempts;
        u32 val;
        unsigned int width, ext_mode, slow_mode;
        adapter_t *adapter = mc4->adapter;

        /* Power up the FCRAMs. */
        val = t1_read_reg_4(adapter, A_MC4_CFG);
        t1_write_reg_4(adapter, A_MC4_CFG, val | F_POWER_UP);
        val = t1_read_reg_4(adapter, A_MC4_CFG);               /* flush */

        if (is_MC4A(adapter)) {
                slow_mode = val & F_MC4A_SLOW;
                width = G_MC4A_WIDTH(val);

                /* If we're not in slow mode, we are using the DLLs */
                if (!slow_mode) {
                        /* Clear Reset */
                        val = t1_read_reg_4(adapter, A_MC4_STROBE);
                        t1_write_reg_4(adapter, A_MC4_STROBE,
                                       val & ~F_SLAVE_DLL_RESET);

                        /* Wait for slave DLLs to lock */
                        DELAY_US(2 * 512 / (mc4_clock / 1000000) + 1);
                }
        } else {
                slow_mode = val & F_MC4_SLOW;
                width = !!(val & F_MC4_NARROW);

                /* Initializes the master DLL and slave delay lines. */
                if (t1_is_asic(adapter) && !slow_mode) {
                        val = t1_read_reg_4(adapter, A_MC4_STROBE);
                        t1_write_reg_4(adapter, A_MC4_STROBE,
                                       val & ~F_MASTER_DLL_RESET);

                        /* Wait for the master DLL to lock. */
                        attempts = 100;
                        do {
                                DELAY_US(1);
                                val = t1_read_reg_4(adapter, A_MC4_STROBE);
                        } while (!(val & MC4_DLL_DONE) && --attempts);
                        if (!(val & MC4_DLL_DONE)) {
                                CH_ERR("%s: MC4 DLL lock failed\n",
                                       adapter_name(adapter));
                                goto out_fail;
                        }
                }
        }

        mc4->nwords = 4 >> width;

        /* Set the FCRAM output drive strength and enable DLLs if needed */
        ext_mode = t1_is_asic(adapter) && !slow_mode ? 0 : 1;
        if (wrreg_wait(adapter, A_MC4_EXT_MODE, ext_mode))
                goto out_fail;

        /* Specify the FCRAM operating parameters */
        if (wrreg_wait(adapter, A_MC4_MODE, 0x32))
                goto out_fail;

        /* Initiate an immediate refresh and wait for the write to complete. */
        val = t1_read_reg_4(adapter, A_MC4_REFRESH);
        if (wrreg_wait(adapter, A_MC4_REFRESH, val & ~F_REFRESH_ENABLE))
                goto out_fail;

        /* 2nd immediate refresh as before */
        if (wrreg_wait(adapter, A_MC4_REFRESH, val & ~F_REFRESH_ENABLE))
                goto out_fail;

        /* Convert to KHz first to avoid 64-bit division. */
        mc4_clock /= 1000;                            /* Hz->KHz */
        mc4_clock = mc4_clock * 7812 + mc4_clock / 2; /* ns */
        mc4_clock /= 1000000;                         /* KHz->MHz, ns->us */

        /* Enable periodic refresh. */
        t1_write_reg_4(adapter, A_MC4_REFRESH,
                       F_REFRESH_ENABLE | V_REFRESH_DIVISOR(mc4_clock));
        (void) t1_read_reg_4(adapter, A_MC4_REFRESH);    /* flush */

        t1_write_reg_4(adapter, A_MC4_ECC_CNTL,
                       F_ECC_GENERATION_ENABLE | F_ECC_CHECK_ENABLE);

        /* Use the BIST engine to clear all of the MC4 memory. */
        t1_write_reg_4(adapter, A_MC4_BIST_ADDR_BEG, 0);
        t1_write_reg_4(adapter, A_MC4_BIST_ADDR_END, (mc4->size << width) - 1);
        t1_write_reg_4(adapter, A_MC4_BIST_DATA, 0);
        t1_write_reg_4(adapter, A_MC4_BIST_OP, V_OP(1) | 0x1f0);
        (void) t1_read_reg_4(adapter, A_MC4_BIST_OP);              /* flush */

        attempts = 100;
        do {
                DELAY_MS(100);
                val = t1_read_reg_4(adapter, A_MC4_BIST_OP);
        } while ((val & F_BUSY) && --attempts);
        if (val & F_BUSY) {
                CH_ERR("%s: MC4 BIST timed out\n", adapter_name(adapter));
                goto out_fail;
        }

        /* Enable normal memory accesses. */
        val = t1_read_reg_4(adapter, A_MC4_CFG);
        t1_write_reg_4(adapter, A_MC4_CFG, val | F_READY);
        val = t1_read_reg_4(adapter, A_MC4_CFG);               /* flush */
        return 0;

 out_fail:
        return -1;
}

struct pemc4 * __devinit t1_mc4_create(adapter_t *adapter)
{
        struct pemc4 *mc4 = t1_os_malloc_wait_zero(sizeof(*mc4));

        if (mc4) {
                mc4->adapter = adapter;
                mc4->size = mc4_calc_size(adapter);
        }
        return mc4;
}

unsigned int t1_mc4_get_size(struct pemc4 *mc4)
{
        return mc4->size;
}

#define MC4_INT_MASK (F_MC4_CORR_ERR | F_MC4_UNCORR_ERR | F_MC4_ADDR_ERR)
#define MC4_INT_FATAL (F_MC4_UNCORR_ERR | F_MC4_ADDR_ERR)

void t1_mc4_intr_enable(struct pemc4 *mc4)
{
        u32 pl_intr;

        if (t1_is_asic(mc4->adapter)) {
                t1_write_reg_4(mc4->adapter, A_MC4_INT_ENABLE, MC4_INT_MASK);

                pl_intr = t1_read_reg_4(mc4->adapter, A_PL_ENABLE);
                t1_write_reg_4(mc4->adapter, A_PL_ENABLE,
                               pl_intr | F_PL_INTR_MC4);
        }
}

void t1_mc4_intr_disable(struct pemc4 *mc4)
{
        u32 pl_intr;

        if (t1_is_asic(mc4->adapter)) {
                t1_write_reg_4(mc4->adapter, A_MC4_INT_ENABLE, 0);

                pl_intr = t1_read_reg_4(mc4->adapter, A_PL_ENABLE);
                t1_write_reg_4(mc4->adapter, A_PL_ENABLE,
                               pl_intr & ~F_PL_INTR_MC4);
        }
}

void t1_mc4_intr_clear(struct pemc4 *mc4)
{
        if (t1_is_asic(mc4->adapter)) {
                t1_write_reg_4(mc4->adapter, A_MC4_INT_CAUSE, 0xffffffff);
                t1_write_reg_4(mc4->adapter, A_PL_CAUSE, F_PL_INTR_MC4);
        }
}

int t1_mc4_intr_handler(struct pemc4 *mc4)
{
        adapter_t *adapter = mc4->adapter;
        u32 cause = t1_read_reg_4(adapter, A_MC4_INT_CAUSE);

        if (cause & F_MC4_CORR_ERR) {
                mc4->intr_cnt.corr_err++;
                CH_WARN("%s: MC4 correctable error at addr 0x%x, "
                        "data 0x%x 0x%x 0x%x 0x%x 0x%x\n",
                        adapter_name(adapter),
                        G_MC4_CE_ADDR(t1_read_reg_4(adapter, A_MC4_CE_ADDR)),
                        t1_read_reg_4(adapter, A_MC4_CE_DATA0),
                        t1_read_reg_4(adapter, A_MC4_CE_DATA1),
                        t1_read_reg_4(adapter, A_MC4_CE_DATA2),
                        t1_read_reg_4(adapter, A_MC4_CE_DATA3),
                        t1_read_reg_4(adapter, A_MC4_CE_DATA4));
        }

        if (cause & F_MC4_UNCORR_ERR) {
                mc4->intr_cnt.uncorr_err++;
                CH_ALERT("%s: MC4 uncorrectable error at addr 0x%x, "
                         "data 0x%x 0x%x 0x%x 0x%x 0x%x\n",
                         adapter_name(adapter),
                         G_MC4_UE_ADDR(t1_read_reg_4(adapter, A_MC4_UE_ADDR)),
                         t1_read_reg_4(adapter, A_MC4_UE_DATA0),
                         t1_read_reg_4(adapter, A_MC4_UE_DATA1),
                         t1_read_reg_4(adapter, A_MC4_UE_DATA2),
                         t1_read_reg_4(adapter, A_MC4_UE_DATA3),
                         t1_read_reg_4(adapter, A_MC4_UE_DATA4));
        }

        if (cause & F_MC4_ADDR_ERR) {
                mc4->intr_cnt.addr_err++;
                CH_ALERT("%s: MC4 address error\n", adapter_name(adapter));
        }

        if (cause & MC4_INT_FATAL)
                t1_fatal_err(adapter);

        t1_write_reg_4(mc4->adapter, A_MC4_INT_CAUSE, cause);
        return 0;
}

const struct pemc4_intr_counts *t1_mc4_get_intr_counts(struct pemc4 *mc4)
{
        return &mc4->intr_cnt;
}

/*
 * Read n 256-bit words from MC4 starting at word start, using backdoor
 * accesses.
 */
int t1_mc4_bd_read(struct pemc4 *mc4, unsigned int start, unsigned int n,
                   u32 *buf)
{
        adapter_t *adap = mc4->adapter;
        unsigned int size256 = mc4->size / 32, c = 8 / mc4->nwords, i;

        if (start >= size256 || start + n > size256)
                return -EINVAL;

        for (i = 8, start *= 16 * c, n *= c; n; --n, start += 16) {
                int attempts = 10;
                u32 val;

                t1_write_reg_4(adap, A_MC4_BD_ADDR, start);
                t1_write_reg_4(adap, A_MC4_BD_OP, 0);
                val = t1_read_reg_4(adap, A_MC4_BD_OP);
                while ((val & F_BUSY) && attempts--)
                        val = t1_read_reg_4(adap, A_MC4_BD_OP);

                if (val & F_BUSY)
                        return -EIO;

                buf[--i] = t1_read_reg_4(adap, A_MC4_BD_DATA3);
                if (mc4->nwords >= 2)
                        buf[--i] = t1_read_reg_4(adap, A_MC4_BD_DATA2);
                if (mc4->nwords == 4) {
                        buf[--i] = t1_read_reg_4(adap, A_MC4_BD_DATA1);
                        buf[--i] = t1_read_reg_4(adap, A_MC4_BD_DATA0);
                }
                if (i == 0) {
                        i = 8;
                        buf += 8;
                }
        }
        return 0;
}