root/usr/src/uts/common/io/ipw/ipw2100_hw.c
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 2004
 *      Damien Bergamini <damien.bergamini@free.fr>. 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 unmodified, 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 AUTHOR 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 AUTHOR 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.
 */

/*
 * Intel Wireless PRO/2100 mini-PCI adapter driver
 * ipw2100_hw.c is used to handle hardware operation and firmware operations.
 */
#include <sys/types.h>
#include <sys/byteorder.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/note.h>
#include <sys/stream.h>
#include <sys/strsun.h>

#include "ipw2100.h"
#include "ipw2100_impl.h"

/*
 * Hardware related operations
 */
#define IPW2100_EEPROM_SHIFT_D  (2)
#define IPW2100_EEPROM_SHIFT_Q  (4)

#define IPW2100_EEPROM_C        (1 << 0)
#define IPW2100_EEPROM_S        (1 << 1)
#define IPW2100_EEPROM_D        (1 << IPW2100_EEPROM_SHIFT_D)
#define IPW2100_EEPROM_Q        (1 << IPW2100_EEPROM_SHIFT_Q)

uint8_t
ipw2100_csr_get8(struct ipw2100_softc *sc, uint32_t off)
{
        return (ddi_get8(sc->sc_ioh, (uint8_t *)(sc->sc_regs + off)));
}

uint16_t
ipw2100_csr_get16(struct ipw2100_softc *sc, uint32_t off)
{
        return (ddi_get16(sc->sc_ioh,
            (uint16_t *)((uintptr_t)sc->sc_regs + off)));
}

uint32_t
ipw2100_csr_get32(struct ipw2100_softc *sc, uint32_t off)
{
        return (ddi_get32(sc->sc_ioh,
            (uint32_t *)((uintptr_t)sc->sc_regs + off)));
}

void
ipw2100_csr_rep_get16(struct ipw2100_softc *sc,
        uint32_t off, uint16_t *buf, size_t cnt)
{
        ddi_rep_get16(sc->sc_ioh, buf,
            (uint16_t *)((uintptr_t)sc->sc_regs + off),
            cnt, DDI_DEV_NO_AUTOINCR);
}

void
ipw2100_csr_put8(struct ipw2100_softc *sc, uint32_t off, uint8_t val)
{
        ddi_put8(sc->sc_ioh, (uint8_t *)(sc->sc_regs + off), val);
}

void
ipw2100_csr_put16(struct ipw2100_softc *sc, uint32_t off, uint16_t val)
{
        ddi_put16(sc->sc_ioh,
            (uint16_t *)((uintptr_t)sc->sc_regs + off), val);
}

void
ipw2100_csr_put32(struct ipw2100_softc *sc, uint32_t off, uint32_t val)
{
        ddi_put32(sc->sc_ioh,
            (uint32_t *)((uintptr_t)sc->sc_regs + off), val);
}

void
ipw2100_csr_rep_put8(struct ipw2100_softc *sc,
        uint32_t off, uint8_t *buf, size_t cnt)
{
        ddi_rep_put8(sc->sc_ioh, buf, (uint8_t *)(sc->sc_regs + off),
            cnt, DDI_DEV_NO_AUTOINCR);
}

uint8_t
ipw2100_imem_get8(struct ipw2100_softc *sc, int32_t addr)
{
        ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr);

        return (ipw2100_csr_get8(sc, IPW2100_CSR_INDIRECT_DATA));
}

uint16_t
ipw2100_imem_get16(struct ipw2100_softc *sc, uint32_t addr)
{
        ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr);

        return (ipw2100_csr_get16(sc, IPW2100_CSR_INDIRECT_DATA));
}

uint32_t
ipw2100_imem_get32(struct ipw2100_softc *sc, uint32_t addr)
{
        ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr);

        return (ipw2100_csr_get32(sc, IPW2100_CSR_INDIRECT_DATA));
}

void
ipw2100_imem_rep_get16(struct ipw2100_softc *sc,
        uint32_t addr, uint16_t *buf, size_t cnt)
{
        ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr);
        ipw2100_csr_rep_get16(sc, IPW2100_CSR_INDIRECT_DATA, buf, cnt);
}

void
ipw2100_imem_put8(struct ipw2100_softc *sc, uint32_t addr, uint8_t val)
{
        ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr);
        ipw2100_csr_put8(sc, IPW2100_CSR_INDIRECT_DATA, val);
}

void
ipw2100_imem_put16(struct ipw2100_softc *sc, uint32_t addr, uint16_t val)
{
        ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr);
        ipw2100_csr_put16(sc, IPW2100_CSR_INDIRECT_DATA, val);
}

void
ipw2100_imem_put32(struct ipw2100_softc *sc, uint32_t addr, uint32_t val)
{
        ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr);
        ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_DATA, val);
}

void
ipw2100_imem_rep_put8(struct ipw2100_softc *sc,
        uint32_t addr, uint8_t *buf, size_t cnt)
{
        ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr);
        ipw2100_csr_rep_put8(sc, IPW2100_CSR_INDIRECT_DATA, buf, cnt);
}

void
ipw2100_imem_getbuf(struct ipw2100_softc *sc,
        uint32_t addr, uint8_t *buf, size_t cnt)
{
        for (; cnt > 0; addr++, buf++, cnt--) {
                ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr & ~3);
                *buf = ipw2100_csr_get8(sc,
                    IPW2100_CSR_INDIRECT_DATA +(addr & 3));
        }
}

void
ipw2100_imem_putbuf(struct ipw2100_softc *sc,
        uint32_t addr, uint8_t *buf, size_t cnt)
{
        for (; cnt > 0; addr++, buf++, cnt--) {
                ipw2100_csr_put32(sc, IPW2100_CSR_INDIRECT_ADDR, addr & ~3);
                ipw2100_csr_put8(sc,
                    IPW2100_CSR_INDIRECT_DATA +(addr & 3), *buf);
        }
}

void
ipw2100_rom_control(struct ipw2100_softc *sc, uint32_t val)
{
        ipw2100_imem_put32(sc, IPW2100_IMEM_EEPROM_CTL, val);
        drv_usecwait(IPW2100_EEPROM_DELAY);
}


uint8_t
ipw2100_table1_get8(struct ipw2100_softc *sc, uint32_t off)
{
        uint32_t addr = ipw2100_imem_get32(sc, sc->sc_table1_base + off);
        return (ipw2100_imem_get8(sc, addr));
}

uint32_t
ipw2100_table1_get32(struct ipw2100_softc *sc, uint32_t off)
{
        uint32_t addr = ipw2100_imem_get32(sc, sc->sc_table1_base + off);
        return (ipw2100_imem_get32(sc, addr));
}

void
ipw2100_table1_put32(struct ipw2100_softc *sc, uint32_t off, uint32_t val)
{
        uint32_t addr = ipw2100_imem_get32(sc, sc->sc_table1_base + off);
        ipw2100_imem_put32(sc, addr, val);
}

int
ipw2100_table2_getbuf(struct ipw2100_softc *sc,
        uint32_t off, uint8_t *buf, uint32_t *len)
{
        uint32_t        addr, info;
        uint16_t        cnt, size;
        uint32_t        total;

        addr = ipw2100_imem_get32(sc, sc->sc_table2_base + off);
        info = ipw2100_imem_get32(sc,
            sc->sc_table2_base + off + sizeof (uint32_t));

        cnt = info >> 16;
        size = info & 0xffff;
        total = cnt * size;

        if (total > *len) {
                IPW2100_WARN((sc->sc_dip, CE_WARN,
                    "ipw2100_table2_getbuf(): invalid table offset = 0x%08x\n",
                    off));
                return (DDI_FAILURE);
        }

        *len = total;
        ipw2100_imem_getbuf(sc, addr, buf, total);

        return (DDI_SUCCESS);
}

uint16_t
ipw2100_rom_get16(struct ipw2100_softc *sc, uint8_t addr)
{
        uint32_t        tmp;
        uint16_t        val;
        int             n;

        /*
         * According to i2c bus protocol to set them.
         */
        /* clock */
        ipw2100_rom_control(sc, 0);
        ipw2100_rom_control(sc, IPW2100_EEPROM_S);
        ipw2100_rom_control(sc, IPW2100_EEPROM_S | IPW2100_EEPROM_C);
        ipw2100_rom_control(sc, IPW2100_EEPROM_S);
        /* start bit */
        ipw2100_rom_control(sc, IPW2100_EEPROM_S | IPW2100_EEPROM_D);
        ipw2100_rom_control(sc, IPW2100_EEPROM_S
            | IPW2100_EEPROM_D | IPW2100_EEPROM_C);
        /* read opcode */
        ipw2100_rom_control(sc, IPW2100_EEPROM_S | IPW2100_EEPROM_D);
        ipw2100_rom_control(sc, IPW2100_EEPROM_S
            | IPW2100_EEPROM_D | IPW2100_EEPROM_C);
        ipw2100_rom_control(sc, IPW2100_EEPROM_S);
        ipw2100_rom_control(sc, IPW2100_EEPROM_S | IPW2100_EEPROM_C);
        /*
         * address, totally 8 bits, defined by hardware, push from MSB to LSB
         */
        for (n = 7; n >= 0; n--) {
                ipw2100_rom_control(sc, IPW2100_EEPROM_S
                    |(((addr >> n) & 1) << IPW2100_EEPROM_SHIFT_D));
                ipw2100_rom_control(sc, IPW2100_EEPROM_S
                    |(((addr >> n) & 1) << IPW2100_EEPROM_SHIFT_D)
                    | IPW2100_EEPROM_C);
        }

        ipw2100_rom_control(sc, IPW2100_EEPROM_S);

        /*
         * data, totally 16 bits, defined by hardware, push from MSB to LSB
         */
        val = 0;
        for (n = 15; n >= 0; n--) {
                ipw2100_rom_control(sc, IPW2100_EEPROM_S | IPW2100_EEPROM_C);
                ipw2100_rom_control(sc, IPW2100_EEPROM_S);
                tmp = ipw2100_imem_get32(sc, IPW2100_IMEM_EEPROM_CTL);
                val |= ((tmp & IPW2100_EEPROM_Q)
                    >> IPW2100_EEPROM_SHIFT_Q) << n;
        }

        ipw2100_rom_control(sc, 0);

        /* clear chip select and clock */
        ipw2100_rom_control(sc, IPW2100_EEPROM_S);
        ipw2100_rom_control(sc, 0);
        ipw2100_rom_control(sc, IPW2100_EEPROM_C);

        return (LE_16(val));
}


/*
 * Firmware related operations
 */
#define IPW2100_FW_MAJOR_VERSION (1)
#define IPW2100_FW_MINOR_VERSION (3)

#define IPW2100_FW_MAJOR(x)((x) & 0xff)
#define IPW2100_FW_MINOR(x)(((x) & 0xff) >> 8)

/*
 * The firware was issued by Intel as binary which need to be loaded
 * to hardware when card is initiated, or when fatal error happened,
 * or when the chip need be reset.
 */
static uint8_t ipw2100_firmware_bin [] = {
#include "fw-ipw2100/ipw2100-1.3.fw.hex"
};

int
ipw2100_cache_firmware(struct ipw2100_softc *sc)
{
        uint8_t                         *bin = ipw2100_firmware_bin;
        struct ipw2100_firmware_hdr     *h = (struct ipw2100_firmware_hdr *)bin;

        IPW2100_DBG(IPW2100_DBG_FW, (sc->sc_dip, CE_CONT,
            "ipw2100_cache_firmwares(): enter\n"));

        sc->sc_fw.bin_base  = bin;
        sc->sc_fw.bin_size  = sizeof (ipw2100_firmware_bin);

        if (IPW2100_FW_MAJOR(h->version) != IPW2100_FW_MAJOR_VERSION) {
                IPW2100_WARN((sc->sc_dip, CE_WARN,
                    "ipw2100_cache_firmware(): image not compatible, %u\n",
                    h->version));
                return (DDI_FAILURE);
        }

        sc->sc_fw.fw_base = bin + sizeof (struct ipw2100_firmware_hdr);
        sc->sc_fw.fw_size = LE_32(h->fw_size);
        sc->sc_fw.uc_base = sc->sc_fw.fw_base + sc->sc_fw.fw_size;
        sc->sc_fw.uc_size = LE_32(h->uc_size);

        sc->sc_flags |= IPW2100_FLAG_FW_CACHED;

        IPW2100_DBG(IPW2100_DBG_FW, (sc->sc_dip, CE_CONT,
            "ipw2100_cache_firmware(): exit\n"));

        return (DDI_SUCCESS);
}

/*
 * If user-land firmware loading is supported, this routine
 * free kmemory if sc->sc_fw.bin_base & sc->sc_fw.bin_size are
 * not empty.
 */
int
ipw2100_free_firmware(struct ipw2100_softc *sc)
{
        sc->sc_flags &= ~IPW2100_FLAG_FW_CACHED;

        return (DDI_SUCCESS);
}

/*
 * the following routines load code onto ipw2100 hardware
 */
int
ipw2100_load_uc(struct ipw2100_softc *sc)
{
        int     ntries;

        ipw2100_imem_put32(sc, 0x3000e0, 0x80000000);
        ipw2100_csr_put32(sc, IPW2100_CSR_RST, 0);

        ipw2100_imem_put16(sc, 0x220000, 0x0703);
        ipw2100_imem_put16(sc, 0x220000, 0x0707);

        ipw2100_imem_put8(sc, 0x210014, 0x72);
        ipw2100_imem_put8(sc, 0x210014, 0x72);

        ipw2100_imem_put8(sc, 0x210000, 0x40);
        ipw2100_imem_put8(sc, 0x210000, 0x00);
        ipw2100_imem_put8(sc, 0x210000, 0x40);

        ipw2100_imem_rep_put8(sc, 0x210010,
            sc->sc_fw.uc_base, sc->sc_fw.uc_size);

        ipw2100_imem_put8(sc, 0x210000, 0x00);
        ipw2100_imem_put8(sc, 0x210000, 0x00);
        ipw2100_imem_put8(sc, 0x210000, 0x80);

        ipw2100_imem_put16(sc, 0x220000, 0x0703);
        ipw2100_imem_put16(sc, 0x220000, 0x0707);

        ipw2100_imem_put8(sc, 0x210014, 0x72);
        ipw2100_imem_put8(sc, 0x210014, 0x72);

        ipw2100_imem_put8(sc, 0x210000, 0x00);
        ipw2100_imem_put8(sc, 0x210000, 0x80);

        /* try many times */
        for (ntries = 0; ntries < 5000; ntries++) {
                if (ipw2100_imem_get8(sc, 0x210000) & 1)
                        break;
                drv_usecwait(1000); /* wait for a while */
        }
        if (ntries == 5000)
                return (DDI_FAILURE);

        ipw2100_imem_put32(sc, 0x3000e0, 0);

        return (DDI_SUCCESS);
}

int
ipw2100_load_fw(struct ipw2100_softc *sc)
{
        uint8_t         *p, *e;
        uint32_t        dst;
        uint16_t        len;
        clock_t         clk;

        IPW2100_DBG(IPW2100_DBG_FW, (sc->sc_dip, CE_CONT,
            "ipw2100_load_fw(): enter\n"));

        p = sc->sc_fw.fw_base;
        e = sc->sc_fw.fw_base + sc->sc_fw.fw_size;
        while (p < e) {
                /*
                 * each block is organized as <DST,LEN,DATA>
                 */
                if ((p + sizeof (dst) + sizeof (len)) > e) {
                        IPW2100_WARN((sc->sc_dip, CE_CONT,
                            "ipw2100_load_fw(): invalid firmware image\n"));
                        return (DDI_FAILURE);
                }
                dst = LE_32(*((uint32_t *)(uintptr_t)p)); p += sizeof (dst);
                len = LE_16(*((uint16_t *)(uintptr_t)p)); p += sizeof (len);
                if ((p + len) > e) {
                        IPW2100_WARN((sc->sc_dip, CE_CONT,
                            "ipw2100_load_fw(): invalid firmware image\n"));
                        return (DDI_FAILURE);
                }

                ipw2100_imem_putbuf(sc, dst, p, len);
                p += len;
        }

        ipw2100_csr_put32(sc, IPW2100_CSR_IO,
            IPW2100_IO_GPIO1_ENABLE | IPW2100_IO_GPIO3_MASK |
            IPW2100_IO_LED_OFF);

        mutex_enter(&sc->sc_ilock);

        /*
         * enable all interrupts
         */
        ipw2100_csr_put32(sc, IPW2100_CSR_INTR_MASK, IPW2100_INTR_MASK_ALL);

        ipw2100_csr_put32(sc, IPW2100_CSR_RST, 0);
        ipw2100_csr_put32(sc, IPW2100_CSR_CTL,
            ipw2100_csr_get32(sc, IPW2100_CSR_CTL) | IPW2100_CTL_ALLOW_STANDBY);

        /*
         * wait for interrupt to notify fw initialization is done
         */
        clk = drv_usectohz(5000000);  /* 5 second */
        while (!(sc->sc_flags & IPW2100_FLAG_FW_INITED)) {
                /*
                 * wait longer for the fw  initialized
                 */
                if (cv_reltimedwait(&sc->sc_fw_cond, &sc->sc_ilock, clk,
                    TR_CLOCK_TICK) < 0)
                        break;
        }
        mutex_exit(&sc->sc_ilock);

        ipw2100_csr_put32(sc, IPW2100_CSR_IO,
            ipw2100_csr_get32(sc, IPW2100_CSR_IO) |
            IPW2100_IO_GPIO1_MASK | IPW2100_IO_GPIO3_MASK);

        if (!(sc->sc_flags & IPW2100_FLAG_FW_INITED)) {
                IPW2100_DBG(IPW2100_DBG_FW, (sc->sc_dip, CE_CONT,
                    "ipw2100_load_fw(): exit, init failed\n"));
                return (DDI_FAILURE);
        }

        IPW2100_DBG(IPW2100_DBG_FW, (sc->sc_dip, CE_CONT,
            "ipw2100_load_fw(): exit\n"));
        return (DDI_SUCCESS);
}