root/usr/src/uts/common/io/chxge/com/mc3.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 "common.h"
#include "regs.h"
#include "mc3.h"

#ifdef CONFIG_CHELSIO_T1_1G
# include "fpga_defs.h"
#endif

struct pemc3 {
        adapter_t *adapter;
        unsigned int size;
        struct pemc3_intr_counts intr_cnt;
};

#define MC3_INTR_MASK (F_MC3_CORR_ERR | F_MC3_UNCORR_ERR | \
                       V_MC3_PARITY_ERR(M_MC3_PARITY_ERR) | F_MC3_ADDR_ERR)
#define MC3_INTR_FATAL (F_MC3_UNCORR_ERR | V_MC3_PARITY_ERR(M_MC3_PARITY_ERR) | F_MC3_ADDR_ERR)

void t1_mc3_intr_enable(struct pemc3 *mc3)
{
        u32 en = t1_read_reg_4(mc3->adapter, A_PL_ENABLE);

        if (t1_is_asic(mc3->adapter)) {
                t1_write_reg_4(mc3->adapter, A_MC3_INT_ENABLE, MC3_INTR_MASK);
                t1_write_reg_4(mc3->adapter, A_PL_ENABLE, en | F_PL_INTR_MC3);
#ifdef CONFIG_CHELSIO_T1_1G
        } else {
                t1_write_reg_4(mc3->adapter, FPGA_MC3_REG_INTRENABLE,
                               MC3_INTR_MASK);
                t1_write_reg_4(mc3->adapter, A_PL_ENABLE,
                               en | FPGA_PCIX_INTERRUPT_MC3);
#endif
        }
}

void t1_mc3_intr_disable(struct pemc3 *mc3)
{
        u32 pl_intr = t1_read_reg_4(mc3->adapter, A_PL_ENABLE);

        if (t1_is_asic(mc3->adapter)) {
                t1_write_reg_4(mc3->adapter, A_MC3_INT_ENABLE, 0);
                t1_write_reg_4(mc3->adapter, A_PL_ENABLE,
                               pl_intr & ~F_PL_INTR_MC3);
#ifdef CONFIG_CHELSIO_T1_1G
        } else {
                t1_write_reg_4(mc3->adapter, FPGA_MC3_REG_INTRENABLE, 0);
                t1_write_reg_4(mc3->adapter, A_PL_ENABLE,
                               pl_intr & ~FPGA_PCIX_INTERRUPT_MC3);
#endif
        }
}

void t1_mc3_intr_clear(struct pemc3 *mc3)
{
        if (t1_is_asic(mc3->adapter)) {
                if (t1_is_T1B(mc3->adapter)) {
                        /*
                         * Workaround for T1B bug: we must write to enable
                         * register to clear interrupts.
                         */
                        u32 old_en;

                        old_en = t1_read_reg_4(mc3->adapter, A_MC3_INT_ENABLE);
                        t1_write_reg_4(mc3->adapter, A_MC3_INT_ENABLE,
                                       0xffffffff);
                        t1_write_reg_4(mc3->adapter, A_MC3_INT_ENABLE, old_en);
                } else
                        t1_write_reg_4(mc3->adapter, A_MC3_INT_CAUSE,
                                       0xffffffff);

                t1_write_reg_4(mc3->adapter, A_PL_CAUSE, F_PL_INTR_MC3);
#ifdef CONFIG_CHELSIO_T1_1G
        } else {
                t1_write_reg_4(mc3->adapter, FPGA_MC3_REG_INTRCAUSE,
                               0xffffffff);
                t1_write_reg_4(mc3->adapter, A_PL_CAUSE,
                               FPGA_PCIX_INTERRUPT_MC3);
#endif
        }
}

int t1_mc3_intr_handler(struct pemc3 *mc3)
{
        adapter_t *adapter = mc3->adapter;
        int cause_reg = A_MC3_INT_CAUSE;
        u32 cause;

#ifdef CONFIG_CHELSIO_T1_1G
        if (!t1_is_asic(adapter))
                cause_reg = FPGA_MC3_REG_INTRCAUSE;
#endif
        cause = t1_read_reg_4(adapter, cause_reg);

        if (cause & F_MC3_CORR_ERR) {
                mc3->intr_cnt.corr_err++;
                CH_WARN("%s: MC3 correctable error at addr 0x%x, "
                        "data 0x%x 0x%x 0x%x 0x%x 0x%x\n",
                        adapter_name(adapter),
                        G_MC3_CE_ADDR(t1_read_reg_4(adapter, A_MC3_CE_ADDR)),
                        t1_read_reg_4(adapter, A_MC3_CE_DATA0),
                        t1_read_reg_4(adapter, A_MC3_CE_DATA1),
                        t1_read_reg_4(adapter, A_MC3_CE_DATA2),
                        t1_read_reg_4(adapter, A_MC3_CE_DATA3),
                        t1_read_reg_4(adapter, A_MC3_CE_DATA4));
        }

        if (cause & F_MC3_UNCORR_ERR) {
                mc3->intr_cnt.uncorr_err++;
                CH_ALERT("%s: MC3 uncorrectable error at addr 0x%x, "
                         "data 0x%x 0x%x 0x%x 0x%x 0x%x\n",
                         adapter_name(adapter),
                         G_MC3_UE_ADDR(t1_read_reg_4(adapter, A_MC3_UE_ADDR)),
                         t1_read_reg_4(adapter, A_MC3_UE_DATA0),
                         t1_read_reg_4(adapter, A_MC3_UE_DATA1),
                         t1_read_reg_4(adapter, A_MC3_UE_DATA2),
                         t1_read_reg_4(adapter, A_MC3_UE_DATA3),
                         t1_read_reg_4(adapter, A_MC3_UE_DATA4));
        }

        if (G_MC3_PARITY_ERR(cause)) {
                mc3->intr_cnt.parity_err++;
                CH_ALERT("%s: MC3 parity error 0x%x\n", adapter_name(adapter),
                         G_MC3_PARITY_ERR(cause));
        }

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

        if (cause & MC3_INTR_FATAL)
                t1_fatal_err(adapter);

        if (t1_is_T1B(adapter)) {
                /*
                 * Workaround for T1B bug: we must write to enable register to
                 * clear interrupts.
                 */
                t1_write_reg_4(adapter, A_MC3_INT_ENABLE, cause);
                /* restore enable */
                t1_write_reg_4(adapter, A_MC3_INT_ENABLE, MC3_INTR_MASK);
        } else
                t1_write_reg_4(adapter, cause_reg, cause);

        return 0;
}

#define is_MC3A(adapter) (!t1_is_T1B(adapter))

/*
 * 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.
 * 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)
{
        t1_write_reg_4(adapter, addr, val);
        val = t1_read_reg_4(adapter, addr);                   /* flush */
        if (!(t1_read_reg_4(adapter, addr) & F_BUSY))
                return 0;
        CH_ERR("%s: write to MC3 register 0x%x timed out\n",
               adapter_name(adapter), addr);
        return -EIO;
}

#define MC3_DLL_DONE (F_MASTER_DLL_LOCKED | F_MASTER_DLL_MAX_TAP_COUNT)

int t1_mc3_init(struct pemc3 *mc3, unsigned int mc3_clock)
{
        u32 val;
        unsigned int width, fast_asic, attempts;
        adapter_t *adapter = mc3->adapter;

        /* Check to see if ASIC is running in slow mode. */
        val = t1_read_reg_4(adapter, A_MC3_CFG);
        width = is_MC3A(adapter) ? G_MC3_WIDTH(val) : 0;
        fast_asic = t1_is_asic(adapter) && !(val & F_MC3_SLOW);

        val &= ~(V_MC3_BANK_CYCLE(M_MC3_BANK_CYCLE) |
                 V_REFRESH_CYCLE(M_REFRESH_CYCLE) |
                 V_PRECHARGE_CYCLE(M_PRECHARGE_CYCLE) |
                 F_ACTIVE_TO_READ_WRITE_DELAY |
                 V_ACTIVE_TO_PRECHARGE_DELAY(M_ACTIVE_TO_PRECHARGE_DELAY) |
                 V_WRITE_RECOVERY_DELAY(M_WRITE_RECOVERY_DELAY));

        if (mc3_clock <= 100000000)
                val |= V_MC3_BANK_CYCLE(7) | V_REFRESH_CYCLE(4) |
                        V_PRECHARGE_CYCLE(2) | V_ACTIVE_TO_PRECHARGE_DELAY(5) |
                        V_WRITE_RECOVERY_DELAY(2);
        else if (mc3_clock <= 133000000)
                val |= V_MC3_BANK_CYCLE(9) | V_REFRESH_CYCLE(5) |
                        V_PRECHARGE_CYCLE(3) | F_ACTIVE_TO_READ_WRITE_DELAY |
                        V_ACTIVE_TO_PRECHARGE_DELAY(6) |
                        V_WRITE_RECOVERY_DELAY(2);
        else
                val |= V_MC3_BANK_CYCLE(0xA) | V_REFRESH_CYCLE(6) |
                        V_PRECHARGE_CYCLE(3) | F_ACTIVE_TO_READ_WRITE_DELAY |
                        V_ACTIVE_TO_PRECHARGE_DELAY(7) |
                        V_WRITE_RECOVERY_DELAY(3);
        t1_write_reg_4(adapter, A_MC3_CFG, val);

        val = t1_read_reg_4(adapter, A_MC3_CFG);
        t1_write_reg_4(adapter, A_MC3_CFG, val | F_CLK_ENABLE);
        val = t1_read_reg_4(adapter, A_MC3_CFG);                 /* flush */

        if (fast_asic) {                                     /* setup DLLs */
                val = t1_read_reg_4(adapter, A_MC3_STROBE);
                if (is_MC3A(adapter)) {
                        t1_write_reg_4(adapter, A_MC3_STROBE,
                                       val & ~F_SLAVE_DLL_RESET);

                        /* Wait for slave DLLs to lock */
                        DELAY_US(2 * 512 / (mc3_clock / 1000000) + 1);
                } else {
                        /* Initialize the master DLL and slave delay lines. */
                        t1_write_reg_4(adapter, A_MC3_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_MC3_STROBE);
                        } while (!(val & MC3_DLL_DONE) && --attempts);
                        if (!(val & MC3_DLL_DONE)) {
                                CH_ERR("%s: MC3 DLL lock failed\n",
                                       adapter_name(adapter));
                                goto out_fail;
                        }
                }
        }

        /* Initiate a precharge and wait for the precharge to complete. */
        if (wrreg_wait(adapter, A_MC3_PRECHARG, 0))
                goto out_fail;

        /* Set the SDRAM output drive strength and enable DLLs if needed */
        if (wrreg_wait(adapter, A_MC3_EXT_MODE, fast_asic ? 0 : 1))
                goto out_fail;

        /* Specify the SDRAM operating parameters. */
        if (wrreg_wait(adapter, A_MC3_MODE, fast_asic ? 0x161 : 0x21))
                goto out_fail;

        /* Initiate a precharge and wait for the precharge to complete. */
        if (wrreg_wait(adapter, A_MC3_PRECHARG, 0))
                goto out_fail;

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

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

        /* Specify the SDRAM operating parameters. */
        if (wrreg_wait(adapter, A_MC3_MODE, fast_asic ? 0x61 : 0x21))
                goto out_fail;

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

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

        t1_write_reg_4(adapter, A_MC3_ECC_CNTL,
                       F_ECC_GENERATION_ENABLE | F_ECC_CHECK_ENABLE);

        /* Use the BIST engine to clear MC3 memory and initialize ECC. */
        t1_write_reg_4(adapter, A_MC3_BIST_ADDR_BEG, 0);
        t1_write_reg_4(adapter, A_MC3_BIST_ADDR_END, (mc3->size << width) - 1);
        t1_write_reg_4(adapter, A_MC3_BIST_DATA, 0);
        t1_write_reg_4(adapter, A_MC3_BIST_OP, V_OP(1) | 0x1f0);
        (void) t1_read_reg_4(adapter, A_MC3_BIST_OP);              /* flush */

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

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

 out_fail:
        return -1;
}

static unsigned int __devinit mc3_calc_size(const adapter_t *adapter, u32 cfg)
{
        unsigned int banks = !!(cfg & F_BANKS) + 1;
        unsigned int org = !!(cfg & F_ORGANIZATION) + 1;
        unsigned int density = G_DENSITY(cfg);

        unsigned int capacity_in_MB = is_MC3A(adapter) ?
                ((256 << density) * banks) / (org << G_MC3_WIDTH(cfg)) :
                ((128 << density) * (16 / org) * banks) / 8;

        return capacity_in_MB * 1024 * 1024;
}

struct pemc3 * __devinit t1_mc3_create(adapter_t *adapter)
{
        struct pemc3 *mc3 = t1_os_malloc_wait_zero(sizeof(*mc3));

        if (mc3) {
                mc3->adapter = adapter;
                mc3->size = mc3_calc_size(adapter,
                                          t1_read_reg_4(adapter, A_MC3_CFG));
        }
        return mc3;
}

void t1_mc3_destroy(struct pemc3 *mc3)
{
        t1_os_free((void *)mc3, sizeof(*mc3));
}

unsigned int t1_mc3_get_size(struct pemc3 *mc3)
{
        return mc3->size;
}

const struct pemc3_intr_counts *t1_mc3_get_intr_counts(struct pemc3 *mc3)
{
        return &mc3->intr_cnt;
}