root/sys/dev/qlxgb/qla_misc.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2011-2013 Qlogic Corporation
 * All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * File : qla_misc.c
 * Author : David C Somayajulu, Qlogic Corporation, Aliso Viejo, CA 92656.
 */

#include <sys/cdefs.h>
#include "qla_os.h"
#include "qla_reg.h"
#include "qla_hw.h"
#include "qla_def.h"
#include "qla_reg.h"
#include "qla_inline.h"
#include "qla_glbl.h"
#include "qla_dbg.h"

/*
 * structure encapsulating the value to read/write to offchip memory
 */
typedef struct _offchip_mem_val {
        uint32_t data_lo;
        uint32_t data_hi;
        uint32_t data_ulo;
        uint32_t data_uhi;
} offchip_mem_val_t;

#define Q8_ADDR_UNDEFINED               0xFFFFFFFF

/*
 * The index to this table is Bits 20-27 of the indirect register address
 */ 
static uint32_t indirect_to_base_map[] =
        {
                Q8_ADDR_UNDEFINED,      /* 0x00 */
                0x77300000,             /* 0x01 */
                0x29500000,             /* 0x02 */
                0x2A500000,             /* 0x03 */
                Q8_ADDR_UNDEFINED,      /* 0x04 */
                0x0D000000,             /* 0x05 */
                0x1B100000,             /* 0x06 */
                0x0E600000,             /* 0x07 */
                0x0E000000,             /* 0x08 */
                0x0E100000,             /* 0x09 */
                0x0E200000,             /* 0x0A */
                0x0E300000,             /* 0x0B */
                0x42000000,             /* 0x0C */
                0x41700000,             /* 0x0D */
                0x42100000,             /* 0x0E */
                0x34B00000,             /* 0x0F */
                0x40500000,             /* 0x10 */
                0x34000000,             /* 0x11 */
                0x34100000,             /* 0x12 */
                0x34200000,             /* 0x13 */
                0x34300000,             /* 0x14 */
                0x34500000,             /* 0x15 */
                0x34400000,             /* 0x16 */
                0x3C000000,             /* 0x17 */
                0x3C100000,             /* 0x18 */
                0x3C200000,             /* 0x19 */
                0x3C300000,             /* 0x1A */
                Q8_ADDR_UNDEFINED,      /* 0x1B */
                0x3C400000,             /* 0x1C */
                0x41000000,             /* 0x1D */
                Q8_ADDR_UNDEFINED,      /* 0x1E */
                0x0D100000,             /* 0x1F */
                Q8_ADDR_UNDEFINED,      /* 0x20 */
                0x77300000,             /* 0x21 */
                0x41600000,             /* 0x22 */
                Q8_ADDR_UNDEFINED,      /* 0x23 */
                Q8_ADDR_UNDEFINED,      /* 0x24 */
                Q8_ADDR_UNDEFINED,      /* 0x25 */
                Q8_ADDR_UNDEFINED,      /* 0x26 */
                Q8_ADDR_UNDEFINED,      /* 0x27 */
                0x41700000,             /* 0x28 */
                Q8_ADDR_UNDEFINED,      /* 0x29 */
                0x08900000,             /* 0x2A */
                0x70A00000,             /* 0x2B */
                0x70B00000,             /* 0x2C */
                0x70C00000,             /* 0x2D */
                0x08D00000,             /* 0x2E */
                0x08E00000,             /* 0x2F */
                0x70F00000,             /* 0x30 */
                0x40500000,             /* 0x31 */
                0x42000000,             /* 0x32 */
                0x42100000,             /* 0x33 */
                Q8_ADDR_UNDEFINED,      /* 0x34 */
                0x08800000,             /* 0x35 */
                0x09100000,             /* 0x36 */
                0x71200000,             /* 0x37 */
                0x40600000,             /* 0x38 */
                Q8_ADDR_UNDEFINED,      /* 0x39 */
                0x71800000,             /* 0x3A */
                0x19900000,             /* 0x3B */
                0x1A900000,             /* 0x3C */
                Q8_ADDR_UNDEFINED,      /* 0x3D */
                0x34600000,             /* 0x3E */
                Q8_ADDR_UNDEFINED,      /* 0x3F */
        };

/*
 * Address Translation Table for CRB to offsets from PCI BAR0
 */
typedef struct _crb_to_pci {
        uint32_t crb_addr;
        uint32_t pci_addr;
} crb_to_pci_t;

static crb_to_pci_t crbinit_to_pciaddr[] = {
        {(0x088 << 20), (0x035 << 20)},
        {(0x089 << 20), (0x02A << 20)},
        {(0x08D << 20), (0x02E << 20)},
        {(0x08E << 20), (0x02F << 20)},
        {(0x0C6 << 20), (0x023 << 20)},
        {(0x0C7 << 20), (0x024 << 20)},
        {(0x0C8 << 20), (0x025 << 20)},
        {(0x0D0 << 20), (0x005 << 20)},
        {(0x0D1 << 20), (0x01F << 20)},
        {(0x0E0 << 20), (0x008 << 20)},
        {(0x0E1 << 20), (0x009 << 20)},
        {(0x0E2 << 20), (0x00A << 20)},
        {(0x0E3 << 20), (0x00B << 20)},
        {(0x0E6 << 20), (0x007 << 20)},
        {(0x199 << 20), (0x03B << 20)},
        {(0x1B1 << 20), (0x006 << 20)},
        {(0x295 << 20), (0x002 << 20)},
        {(0x29A << 20), (0x000 << 20)},
        {(0x2A5 << 20), (0x003 << 20)},
        {(0x340 << 20), (0x011 << 20)},
        {(0x341 << 20), (0x012 << 20)},
        {(0x342 << 20), (0x013 << 20)},
        {(0x343 << 20), (0x014 << 20)},
        {(0x344 << 20), (0x016 << 20)},
        {(0x345 << 20), (0x015 << 20)},
        {(0x3C0 << 20), (0x017 << 20)},
        {(0x3C1 << 20), (0x018 << 20)},
        {(0x3C2 << 20), (0x019 << 20)},
        {(0x3C3 << 20), (0x01A << 20)},
        {(0x3C4 << 20), (0x01C << 20)},
        {(0x3C5 << 20), (0x01B << 20)},
        {(0x405 << 20), (0x031 << 20)},
        {(0x406 << 20), (0x038 << 20)},
        {(0x410 << 20), (0x01D << 20)},
        {(0x416 << 20), (0x022 << 20)},
        {(0x417 << 20), (0x028 << 20)},
        {(0x420 << 20), (0x032 << 20)},
        {(0x421 << 20), (0x033 << 20)},
        {(0x700 << 20), (0x00C << 20)},
        {(0x701 << 20), (0x00D << 20)},
        {(0x702 << 20), (0x00E << 20)},
        {(0x703 << 20), (0x00F << 20)},
        {(0x704 << 20), (0x010 << 20)},
        {(0x70A << 20), (0x02B << 20)},
        {(0x70B << 20), (0x02C << 20)},
        {(0x70C << 20), (0x02D << 20)},
        {(0x70F << 20), (0x030 << 20)},
        {(0x718 << 20), (0x03A << 20)},
        {(0x758 << 20), (0x026 << 20)},
        {(0x759 << 20), (0x027 << 20)},
        {(0x773 << 20), (0x001 << 20)}
};

#define Q8_INVALID_ADDRESS      (-1)
#define Q8_ADDR_MASK            (0xFFF << 20)

typedef struct _addr_val {
        uint32_t addr;
        uint32_t value;
        uint32_t pci_addr;
        uint32_t ind_addr;
} addr_val_t;

/*
 * Name: qla_rdwr_indreg32
 * Function: Read/Write an Indirect Register
 */
int
qla_rdwr_indreg32(qla_host_t *ha, uint32_t addr, uint32_t *val, uint32_t rd)
{
        uint32_t offset;
        int count = 100;

        offset = (addr & 0xFFF00000) >> 20;

        if (offset > 0x3F) {
                device_printf(ha->pci_dev, "%s: invalid addr 0x%08x\n",
                        __func__, addr);
                return -1;
        }

        offset = indirect_to_base_map[offset];
        if (offset == Q8_ADDR_UNDEFINED) {
                device_printf(ha->pci_dev, "%s: undefined map 0x%08x\n",
                        __func__, addr);
                return -1;
        }

        offset = offset | (addr & 0x000F0000);

        if (qla_sem_lock(ha, Q8_SEM7_LOCK, 0, 0)) {
                device_printf(ha->pci_dev, "%s: SEM7_LOCK failed\n", __func__);
                return (-1);
        }

        WRITE_OFFSET32(ha, Q8_CRB_WINDOW_2M, offset);

        while (offset != (READ_OFFSET32(ha, Q8_CRB_WINDOW_2M))) {
                count--;
                if (!count) {
                        qla_sem_unlock(ha, Q8_SEM7_UNLOCK);
                        return -1;
                }

                qla_mdelay(__func__, 1);
        }

        if (rd) {
                *val = READ_OFFSET32(ha, ((addr & 0xFFFF) | 0x1E0000));
        } else {
                WRITE_OFFSET32(ha, ((addr & 0xFFFF) | 0x1E0000), *val);
        } 

        qla_sem_unlock(ha, Q8_SEM7_UNLOCK);
        return 0;
}

/*
 * Name: qla_rdwr_offchip_mem
 * Function: Read/Write OffChip Memory
 */
static int
qla_rdwr_offchip_mem(qla_host_t *ha, uint64_t addr, offchip_mem_val_t *val,
        uint32_t rd)
{
        uint32_t count = 100;
        uint32_t data;

        WRITE_OFFSET32(ha, Q8_MIU_TEST_AGT_ADDR_LO, (uint32_t)addr);
        WRITE_OFFSET32(ha, Q8_MIU_TEST_AGT_ADDR_HI, (uint32_t)(addr >> 32));

        if (!rd) {
                WRITE_OFFSET32(ha, Q8_MIU_TEST_AGT_WRDATA_LO, val->data_lo);
                WRITE_OFFSET32(ha, Q8_MIU_TEST_AGT_WRDATA_HI, val->data_hi);
                WRITE_OFFSET32(ha, Q8_MIU_TEST_AGT_WRDATA_ULO, val->data_ulo);
                WRITE_OFFSET32(ha, Q8_MIU_TEST_AGT_WRDATA_UHI, val->data_uhi);
                WRITE_OFFSET32(ha, Q8_MIU_TEST_AGT_CTRL, 0x07); /* Write */
        } else {
                WRITE_OFFSET32(ha, Q8_MIU_TEST_AGT_CTRL, 0x03); /* Read */
        }

        while (count--) {
                data = READ_OFFSET32(ha, Q8_MIU_TEST_AGT_CTRL);
                if (!(data & BIT_3)) {
                        if (rd) {
                                val->data_lo = READ_OFFSET32(ha, \
                                                Q8_MIU_TEST_AGT_RDDATA_LO);
                                val->data_hi = READ_OFFSET32(ha, \
                                                Q8_MIU_TEST_AGT_RDDATA_HI);
                                val->data_ulo = READ_OFFSET32(ha, \
                                                Q8_MIU_TEST_AGT_RDDATA_ULO);
                                val->data_uhi = READ_OFFSET32(ha, \
                                                Q8_MIU_TEST_AGT_RDDATA_UHI);
                        }
                        return 0;
                } else 
                        qla_mdelay(__func__, 1);
        }

        device_printf(ha->pci_dev, "%s: failed[0x%08x]\n", __func__, data);
        return (-1);
}

/*
 * Name: qla_rd_flash32
 * Function: Read Flash Memory
 */
int
qla_rd_flash32(qla_host_t *ha, uint32_t addr, uint32_t *data)
{
        uint32_t val;
        uint32_t count = 100;

        if (qla_sem_lock(ha, Q8_SEM2_LOCK, 0, 0)) {
                device_printf(ha->pci_dev, "%s: SEM2_LOCK failed\n", __func__);
                return (-1);
        }
        WRITE_OFFSET32(ha, Q8_ROM_LOCKID, 0xa5a5a5a5);

        val = addr;
        qla_rdwr_indreg32(ha, Q8_ROM_ADDRESS, &val, 0);
        val = 0;
        qla_rdwr_indreg32(ha, Q8_ROM_DUMMY_BYTE_COUNT, &val, 0);
        val = 3;
        qla_rdwr_indreg32(ha, Q8_ROM_ADDR_BYTE_COUNT, &val, 0);

        QLA_USEC_DELAY(100);

        val = ROM_OPCODE_FAST_RD;
        qla_rdwr_indreg32(ha, Q8_ROM_INSTR_OPCODE, &val, 0);

        while (!((val = READ_OFFSET32(ha, Q8_ROM_STATUS)) & BIT_1)) {
                count--;
                if (!count) {
                        qla_sem_unlock(ha, Q8_SEM7_UNLOCK);
                        return -1;
                }
        }

        val = 0;
        qla_rdwr_indreg32(ha, Q8_ROM_DUMMY_BYTE_COUNT, &val, 0);
        qla_rdwr_indreg32(ha, Q8_ROM_ADDR_BYTE_COUNT, &val, 0);

        QLA_USEC_DELAY(100);

        qla_rdwr_indreg32(ha, Q8_ROM_RD_DATA, data, 1);

        qla_sem_unlock(ha, Q8_SEM2_UNLOCK);
        return 0;
}

static int
qla_p3p_sem_lock2(qla_host_t *ha)
{
        if (qla_sem_lock(ha, Q8_SEM2_LOCK, 0, 0)) {
                device_printf(ha->pci_dev, "%s: SEM2_LOCK failed\n", __func__);
                return (-1);
        }
        WRITE_OFFSET32(ha, Q8_ROM_LOCKID, 0xa5a5a5a5);
        return (0);
}

/*
 * Name: qla_int_to_pci_addr_map
 * Function: Convert's Internal(CRB) Address to Indirect Address
 */
static uint32_t
qla_int_to_pci_addr_map(qla_host_t *ha, uint32_t int_addr)
{
        uint32_t crb_to_pci_table_size, i;
        uint32_t addr;

        crb_to_pci_table_size = sizeof(crbinit_to_pciaddr)/sizeof(crb_to_pci_t);
        addr = int_addr & Q8_ADDR_MASK;

        for (i = 0; i < crb_to_pci_table_size; i++) {
                if (crbinit_to_pciaddr[i].crb_addr == addr) {
                        addr = (int_addr & ~Q8_ADDR_MASK) |
                                        crbinit_to_pciaddr[i].pci_addr;
                        return (addr);
                }
        }
        return (Q8_INVALID_ADDRESS);
}

/*
 * Name: qla_filter_pci_addr
 * Function: Filter's out Indirect Addresses which are not writeable
 */
static uint32_t
qla_filter_pci_addr(qla_host_t *ha, uint32_t addr)
{
        if ((addr == Q8_INVALID_ADDRESS) ||
                (addr == 0x00112040) ||
                (addr == 0x00112048) ||
                ((addr & 0xFFFF0FFF) == 0x001100C4) ||
                ((addr & 0xFFFF0FFF) == 0x001100C8) ||
                ((addr & 0x0FF00000) == 0x00200000) ||
                (addr == 0x022021FC) ||
                (addr == 0x0330001C) ||
                (addr == 0x03300024) ||
                (addr == 0x033000A8) ||
                (addr == 0x033000C8) ||
                (addr == 0x033000BC) ||
                ((addr & 0x0FF00000) == 0x03A00000) ||
                (addr == 0x03B0001C))
                return (Q8_INVALID_ADDRESS);
        else
                return (addr); 
}

/*
 * Name: qla_crb_init
 * Function: CRB Initialization - first step in the initialization after reset
 *      Essentially reads the address/value pairs from address = 0x00 and
 *      writes the value into address in the addr/value pair.
 */
static int
qla_crb_init(qla_host_t *ha)
{
        uint32_t val = 0, sig = 0;
        uint32_t offset, count, i;
        addr_val_t *addr_val_map, *avmap;

        qla_rd_flash32(ha, 0, &sig);
        QL_DPRINT2((ha->pci_dev, "%s: val[0] = 0x%08x\n", __func__, sig));

        qla_rd_flash32(ha, 4, &val);
        QL_DPRINT2((ha->pci_dev, "%s: val[4] = 0x%08x\n", __func__, val));

        count = val >> 16;
        offset = val & 0xFFFF;
        offset = offset << 2;

        QL_DPRINT2((ha->pci_dev, "%s: [sig,val]=[0x%08x, 0x%08x] %d pairs\n",
                __func__, sig, val, count));

        addr_val_map = avmap = malloc((sizeof(addr_val_t) * count),
                                        M_QLA8XXXBUF, M_NOWAIT);

        if (addr_val_map == NULL) {
                device_printf(ha->pci_dev, "%s: malloc failed\n", __func__);
                return (-1);
        }
        memset(avmap, 0, (sizeof(addr_val_t) * count));

        count = count << 1;
        for (i = 0; i < count; ) {
                qla_rd_flash32(ha, (offset + (i * 4)), &avmap->value);
                i++;
                qla_rd_flash32(ha, (offset + (i * 4)), &avmap->addr);
                i++;

                avmap->pci_addr = qla_int_to_pci_addr_map(ha, avmap->addr);
                avmap->ind_addr = qla_filter_pci_addr(ha, avmap->pci_addr);

                QL_DPRINT2((ha->pci_dev,
                        "%s: [0x%02x][0x%08x:0x%08x:0x%08x] 0x%08x\n",
                        __func__, (i >> 1), avmap->addr, avmap->pci_addr,
                        avmap->ind_addr, avmap->value));

                if (avmap->ind_addr != Q8_INVALID_ADDRESS) {
                        qla_rdwr_indreg32(ha, avmap->ind_addr, &avmap->value,0);
                        qla_mdelay(__func__, 1);
                }
                avmap++;
        }

        free (addr_val_map, M_QLA8XXXBUF);
        return (0);
}

/*
 * Name: qla_init_peg_regs
 * Function: Protocol Engine Register Initialization
 */
static void
qla_init_peg_regs(qla_host_t *ha)
{
        WRITE_OFFSET32(ha, Q8_PEG_D_RESET1, 0x001E);
        WRITE_OFFSET32(ha, Q8_PEG_D_RESET2, 0x0008);
        WRITE_OFFSET32(ha, Q8_PEG_I_RESET, 0x0008);
        WRITE_OFFSET32(ha, Q8_PEG_0_CLR1, 0x0000);
        WRITE_OFFSET32(ha, Q8_PEG_0_CLR2, 0x0000);
        WRITE_OFFSET32(ha, Q8_PEG_1_CLR1, 0x0000);
        WRITE_OFFSET32(ha, Q8_PEG_1_CLR2, 0x0000);
        WRITE_OFFSET32(ha, Q8_PEG_2_CLR1, 0x0000);
        WRITE_OFFSET32(ha, Q8_PEG_2_CLR2, 0x0000);
        WRITE_OFFSET32(ha, Q8_PEG_3_CLR1, 0x0000);
        WRITE_OFFSET32(ha, Q8_PEG_3_CLR2, 0x0000);
        WRITE_OFFSET32(ha, Q8_PEG_4_CLR1, 0x0000);
        WRITE_OFFSET32(ha, Q8_PEG_4_CLR2, 0x0000);
}

/*
 * Name: qla_load_fw_from_flash
 * Function: Reads the Bootloader from Flash and Loads into Offchip Memory
 */
static void
qla_load_fw_from_flash(qla_host_t *ha)
{
        uint64_t mem_off        = 0x10000;
        uint32_t flash_off      = 0x10000;
        uint32_t count;
        offchip_mem_val_t val;

        /* only bootloader needs to be loaded into memory */
        for (count = 0; count < 0x20000 ; ) {
                qla_rd_flash32(ha, flash_off, &val.data_lo);
                count = count + 4;
                flash_off = flash_off + 4;

                qla_rd_flash32(ha, flash_off, &val.data_hi);
                count = count + 4;
                flash_off = flash_off + 4;

                qla_rd_flash32(ha, flash_off, &val.data_ulo);
                count = count + 4;
                flash_off = flash_off + 4;

                qla_rd_flash32(ha, flash_off, &val.data_uhi);
                count = count + 4;
                flash_off = flash_off + 4;

                qla_rdwr_offchip_mem(ha, mem_off, &val, 0);

                mem_off = mem_off + 16;
        }
        return;
}

/*
 * Name: qla_init_from_flash
 * Function: Performs Initialization which consists of the following sequence
 *      - reset
 *      - CRB Init
 *      - Peg Init
 *      - Read the Bootloader from Flash and Load into Offchip Memory
 *      - Kick start the bootloader which loads the rest of the firmware
 *              and performs the remaining steps in the initialization process.
 */
static int
qla_init_from_flash(qla_host_t *ha)
{
        uint32_t delay = 300;
        uint32_t data;

        qla_hw_reset(ha);
        qla_mdelay(__func__, 100);

        qla_crb_init(ha);
        qla_mdelay(__func__, 10);

        qla_init_peg_regs(ha);
        qla_mdelay(__func__, 10);

        qla_load_fw_from_flash(ha);

        WRITE_OFFSET32(ha, Q8_CMDPEG_STATE, 0x00000000);
        WRITE_OFFSET32(ha, Q8_PEG_0_RESET, 0x00001020);
        WRITE_OFFSET32(ha, Q8_ASIC_RESET, 0x0080001E);
        qla_mdelay(__func__, 100);

        do {
                data = READ_OFFSET32(ha, Q8_CMDPEG_STATE);

                QL_DPRINT2((ha->pci_dev, "%s: func[%d] cmdpegstate 0x%08x\n",
                                __func__, ha->pci_func, data));
                if (data == CMDPEG_PHAN_INIT_COMPLETE) {
                        QL_DPRINT2((ha->pci_dev,
                                "%s: func[%d] init complete\n",
                                __func__, ha->pci_func));
                        return(0);
                }
                qla_mdelay(__func__, 100);
        } while (delay--);

        device_printf(ha->pci_dev,
                "%s: func[%d] Q8_PEG_HALT_STATUS1[0x%08x] STATUS2[0x%08x]"
                " HEARTBEAT[0x%08x] RCVPEG_STATE[0x%08x]"
                " CMDPEG_STATE[0x%08x]\n",
                __func__, ha->pci_func,
                (READ_OFFSET32(ha, Q8_PEG_HALT_STATUS1)),
                (READ_OFFSET32(ha, Q8_PEG_HALT_STATUS2)),
                (READ_OFFSET32(ha, Q8_FIRMWARE_HEARTBEAT)),
                (READ_OFFSET32(ha, Q8_RCVPEG_STATE)), data);

        return (-1);
}

/*
 * Name: qla_init_hw
 * Function: Initializes P3+ hardware.
 */
int
qla_init_hw(qla_host_t *ha)
{
        device_t dev;
        int ret = 0;
        uint32_t val, delay = 300;

        dev = ha->pci_dev;

        QL_DPRINT1((dev, "%s: enter\n", __func__));

        qla_mdelay(__func__, 100);

        if (ha->pci_func & 0x1) {
                while ((ha->pci_func & 0x1) && delay--) {
                        val = READ_OFFSET32(ha, Q8_CMDPEG_STATE);

                        if (val == CMDPEG_PHAN_INIT_COMPLETE) {
                                QL_DPRINT2((dev,
                                        "%s: func = %d init complete\n",
                                        __func__, ha->pci_func));
                                qla_mdelay(__func__, 100);
                                goto qla_init_exit;
                        }
                        qla_mdelay(__func__, 100);
                }
                return (-1);
        }

        val = READ_OFFSET32(ha, Q8_CMDPEG_STATE);

        if (val != CMDPEG_PHAN_INIT_COMPLETE) {
                ret = qla_init_from_flash(ha);
                qla_mdelay(__func__, 100);
        } else {
                ha->fw_ver_major = READ_OFFSET32(ha, Q8_FW_VER_MAJOR);
                ha->fw_ver_minor = READ_OFFSET32(ha, Q8_FW_VER_MINOR);
                ha->fw_ver_sub = READ_OFFSET32(ha, Q8_FW_VER_SUB);

                if (qla_rd_flash32(ha, 0x100004, &val) == 0) {
                        if (((val & 0xFF) != ha->fw_ver_major) ||
                                (((val >> 8) & 0xFF) != ha->fw_ver_minor) ||
                                (((val >> 16) & 0xFF) != ha->fw_ver_sub)) {
                                ret = qla_init_from_flash(ha);
                                qla_mdelay(__func__, 100);
                        }
                }
        }

qla_init_exit:
        ha->fw_ver_major = READ_OFFSET32(ha, Q8_FW_VER_MAJOR);
        ha->fw_ver_minor = READ_OFFSET32(ha, Q8_FW_VER_MINOR);
        ha->fw_ver_sub = READ_OFFSET32(ha, Q8_FW_VER_SUB);
        ha->fw_ver_build = READ_OFFSET32(ha, Q8_FW_VER_BUILD);

        return (ret);
}

static int
qla_wait_for_flash_busy(qla_host_t *ha)
{
        uint32_t count = 100;
        uint32_t val;

        QLA_USEC_DELAY(100);

        while (count--) {
                val = READ_OFFSET32(ha, Q8_ROM_STATUS);

                if (val & BIT_1)
                        return 0;
                qla_mdelay(__func__, 1);
        }
        return -1;
}

static int
qla_flash_write_enable(qla_host_t *ha)
{
        uint32_t val, rval;

        val = 0;
        qla_rdwr_indreg32(ha, Q8_ROM_ADDR_BYTE_COUNT, &val, 0);

        val = ROM_OPCODE_WR_ENABLE;
        qla_rdwr_indreg32(ha, Q8_ROM_INSTR_OPCODE, &val, 0);

        rval = qla_wait_for_flash_busy(ha);

        if (rval)
                device_printf(ha->pci_dev, "%s: failed \n", __func__);

        return (rval);
}

static int
qla_flash_unprotect(qla_host_t *ha)
{
        uint32_t val, rval;

        if (qla_flash_write_enable(ha) != 0) 
                return(-1);

        val = 0;
        qla_rdwr_indreg32(ha, Q8_ROM_WR_DATA, &val, 0);

        val = ROM_OPCODE_WR_STATUS_REG;
        qla_rdwr_indreg32(ha, Q8_ROM_INSTR_OPCODE, &val, 0);

        rval = qla_wait_for_flash_busy(ha);

        if (rval) {
                device_printf(ha->pci_dev, "%s: failed \n", __func__);
                return rval;
        }

        if (qla_flash_write_enable(ha) != 0) 
                return(-1);

        val = 0;
        qla_rdwr_indreg32(ha, Q8_ROM_WR_DATA, &val, 0);

        val = ROM_OPCODE_WR_STATUS_REG;
        qla_rdwr_indreg32(ha, Q8_ROM_INSTR_OPCODE, &val, 0);

        rval = qla_wait_for_flash_busy(ha);

        if (rval)
                device_printf(ha->pci_dev, "%s: failed \n", __func__);

        return rval;
}

static int
qla_flash_protect(qla_host_t *ha)
{
        uint32_t val, rval;

        if (qla_flash_write_enable(ha) != 0) 
                return(-1);

        val = 0x9C;
        qla_rdwr_indreg32(ha, Q8_ROM_WR_DATA, &val, 0);

        val = ROM_OPCODE_WR_STATUS_REG;
        qla_rdwr_indreg32(ha, Q8_ROM_INSTR_OPCODE, &val, 0);

        rval = qla_wait_for_flash_busy(ha);

        if (rval)
                device_printf(ha->pci_dev, "%s: failed \n", __func__);

        return rval;
}

static uint32_t
qla_flash_get_status(qla_host_t *ha)
{
        uint32_t count = 1000;
        uint32_t val, rval;

        while (count--) {
                val = 0;
                qla_rdwr_indreg32(ha, Q8_ROM_ADDR_BYTE_COUNT, &val, 0);
                        
                val = ROM_OPCODE_RD_STATUS_REG;
                qla_rdwr_indreg32(ha, Q8_ROM_INSTR_OPCODE, &val, 0);

                rval = qla_wait_for_flash_busy(ha);

                if (rval == 0) {
                        qla_rdwr_indreg32(ha, Q8_ROM_RD_DATA, &val, 1);

                        if ((val & BIT_0) == 0)
                                return (val);
                }
                qla_mdelay(__func__, 1);
        }
        return -1;
}

static int
qla_wait_for_flash_unprotect(qla_host_t *ha)
{
        uint32_t delay = 1000;

        while (delay--) {
                if (qla_flash_get_status(ha) == 0)
                        return 0;

                qla_mdelay(__func__, 1);
        }

        return -1;
}

static int
qla_wait_for_flash_protect(qla_host_t *ha)
{
        uint32_t delay = 1000;

        while (delay--) {
                if (qla_flash_get_status(ha) == 0x9C)
                        return 0;

                qla_mdelay(__func__, 1);
        }

        return -1;
}

static int
qla_erase_flash_sector(qla_host_t *ha, uint32_t start)
{
        uint32_t val;
        int rval;

        if (qla_flash_write_enable(ha) != 0) 
                return(-1);

        val = start;
        qla_rdwr_indreg32(ha, Q8_ROM_ADDRESS, &val, 0);

        val = 3;
        qla_rdwr_indreg32(ha, Q8_ROM_ADDR_BYTE_COUNT, &val, 0);

        val = ROM_OPCODE_SECTOR_ERASE;
        qla_rdwr_indreg32(ha, Q8_ROM_INSTR_OPCODE, &val, 0);

        rval = qla_wait_for_flash_busy(ha);

        if (rval)
                device_printf(ha->pci_dev, "%s: failed \n", __func__);
        return rval;
}

#define Q8_FLASH_SECTOR_SIZE 0x10000
int
qla_erase_flash(qla_host_t *ha, uint32_t off, uint32_t size)
{
        int rval = 0;
        uint32_t start;

        if (off & (Q8_FLASH_SECTOR_SIZE -1))
                return -1;

        if ((rval = qla_p3p_sem_lock2(ha)))
                goto qla_erase_flash_exit;

        if ((rval = qla_flash_unprotect(ha)))
                goto qla_erase_flash_unlock_exit;

        if ((rval = qla_wait_for_flash_unprotect(ha)))
                goto qla_erase_flash_unlock_exit;

        for (start = off; start < (off + size); start = start + 0x10000) {
                if (qla_erase_flash_sector(ha, start)) {
                        rval = -1;
                        break;
                }
        }

        rval = qla_flash_protect(ha);

qla_erase_flash_unlock_exit:
        qla_sem_unlock(ha, Q8_SEM2_UNLOCK);

qla_erase_flash_exit:
        return (rval);
}

static int
qla_flash_write32(qla_host_t *ha, uint32_t off, uint32_t data)
{
        uint32_t val;
        int rval = 0;

        val = data;
        qla_rdwr_indreg32(ha, Q8_ROM_WR_DATA, &val, 0);

        val = off;
        qla_rdwr_indreg32(ha, Q8_ROM_ADDRESS, &val, 0);

        val = 3;
        qla_rdwr_indreg32(ha, Q8_ROM_ADDR_BYTE_COUNT, &val, 0);

        val = ROM_OPCODE_PROG_PAGE;
        qla_rdwr_indreg32(ha, Q8_ROM_INSTR_OPCODE, &val, 0);

        rval = qla_wait_for_flash_busy(ha);

        if (rval)
                device_printf(ha->pci_dev, "%s: failed \n", __func__);

        return rval;
}

static int
qla_flash_wait_for_write_complete(qla_host_t *ha)
{
        uint32_t val, count = 1000;
        int rval = 0;

        while (count--) {
                val = 0;
                qla_rdwr_indreg32(ha, Q8_ROM_ADDR_BYTE_COUNT, &val, 0);

                val = ROM_OPCODE_RD_STATUS_REG;
                qla_rdwr_indreg32(ha, Q8_ROM_INSTR_OPCODE, &val, 0);

                
                rval = qla_wait_for_flash_busy(ha);

                if (rval == 0) {
                        qla_rdwr_indreg32(ha, Q8_ROM_RD_DATA, &val, 1);

                        if ((val & BIT_0) == 0)
                                return (0);
                }
                qla_mdelay(__func__, 1);
        }
        return -1;
}

static int
qla_flash_write(qla_host_t *ha, uint32_t off, uint32_t data)
{
        if (qla_flash_write_enable(ha) != 0) 
                return(-1);

        if (qla_flash_write32(ha, off, data) != 0)
                return -1;

        if (qla_flash_wait_for_write_complete(ha))
                return -1;

        return 0;
}

static int
qla_flash_write_pattern(qla_host_t *ha, uint32_t off, uint32_t size,
        uint32_t pattern)
{
        int rval = 0;
        uint32_t start;

        if ((rval = qla_p3p_sem_lock2(ha)))
                goto qla_wr_pattern_exit;

        if ((rval = qla_flash_unprotect(ha)))
                goto qla_wr_pattern_unlock_exit;

        if ((rval = qla_wait_for_flash_unprotect(ha)))
                goto qla_wr_pattern_unlock_exit;

        for (start = off; start < (off + size); start = start + 4) {
                if (qla_flash_write(ha, start, pattern)) {
                        rval = -1;
                        break;
                }
        }

        rval = qla_flash_protect(ha);

        if (rval == 0)
                rval = qla_wait_for_flash_protect(ha);

qla_wr_pattern_unlock_exit:
        qla_sem_unlock(ha, Q8_SEM2_UNLOCK);

qla_wr_pattern_exit:
        return (rval);
}

static int
qla_flash_write_data(qla_host_t *ha, uint32_t off, uint32_t size,
        void *data)
{
        int rval = 0;
        uint32_t start;
        uint32_t *data32 = data;

        if ((rval = qla_p3p_sem_lock2(ha)))
                goto qla_wr_pattern_exit;

        if ((rval = qla_flash_unprotect(ha)))
                goto qla_wr_pattern_unlock_exit;

        if ((rval = qla_wait_for_flash_unprotect(ha)))
                goto qla_wr_pattern_unlock_exit;

        for (start = off; start < (off + size); start = start + 4) {
                
                if (*data32 != 0xFFFFFFFF) {
                        if (qla_flash_write(ha, start, *data32)) {
                                rval = -1;
                                break;
                        }
                }
                data32++;
        }

        rval = qla_flash_protect(ha);

        if (rval == 0)
                rval = qla_wait_for_flash_protect(ha);

qla_wr_pattern_unlock_exit:
        qla_sem_unlock(ha, Q8_SEM2_UNLOCK);

qla_wr_pattern_exit:
        return (rval);
}

int
qla_wr_flash_buffer(qla_host_t *ha, uint32_t off, uint32_t size, void *buf,
        uint32_t pattern)
{
        int rval = 0;
        void *data;

        if (size == 0)
                return 0;

        size = size << 2;

        if (buf == NULL) {
                rval = qla_flash_write_pattern(ha, off, size, pattern);
                return (rval);
        }

        if ((data = malloc(size, M_QLA8XXXBUF, M_NOWAIT)) == NULL) {
                device_printf(ha->pci_dev, "%s: malloc failed \n", __func__);
                rval = -1;
                goto qla_wr_flash_buffer_exit;
        }

        if ((rval = copyin(buf, data, size))) {
                device_printf(ha->pci_dev, "%s copyin failed\n", __func__);
                goto qla_wr_flash_buffer_free_exit;
        }

        rval = qla_flash_write_data(ha, off, size, data);

qla_wr_flash_buffer_free_exit:
        free(data, M_QLA8XXXBUF);

qla_wr_flash_buffer_exit:
        return (rval);
}