/* $ZEL: sis1100_eeprom.c,v 1.2 2004/05/27 23:10:19 wuestner Exp $ */

/*
 * Copyright (c) 2003-2004
 * 	Peter Wuestner.  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 AUTHORS 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.
 */

#include "sis1100_sc.h"

#if !defined(__NetBSD__) && ! defined(__linux__)
#error Invalid or Unknown Operating System
#endif

/*
Procedures in this file are used to read and write the serial EEPROM
(FAIRCHILD NM93CS66) which holds values for PLX-Initialisation.

!!Modification of the EEPROM content can render the SIS1100 unusable!!
*/

#define EEP_L16 0x80                    // length of 16 bit words (2048 Bit)
#define EEP_L8          (2*EEP_L16)     // length of bytes

#define EEP_C           0x01            // Clock
#define EEP_S           0x02            // Select
#define EEP_D           0x04            // Data Out
#define EEP_Q           0x08            // Data In
#define EEP_P           0x10            // EEPROM Present
#define EEP_ALEN        8               // Address Field

#define EEPROM (ofs(struct plx9045reg, CNTRL)+3)

static void
clock_eeprom(struct sis1100_softc* sc)
{
        u_int32_t tmp;
        u_int i;

        for (i=0; i < 2; i++) {
                tmp=_plxreadreg_1(sc, EEPROM);
                _plxwritereg_1(sc, EEPROM, tmp|=EEP_C);
        }
        for (i=0; i < 2; i++) {
                tmp=_plxreadreg_1(sc, EEPROM);
                _plxwritereg_1(sc, EEPROM, tmp & ~EEP_C);
        }
}

static void
deselect_eeprom(struct sis1100_softc* sc)
{
        u_int32_t tmp;
        u_int i;

        for (i=0; i < 5; i++) {
                tmp=_plxreadreg_1(sc, EEPROM);
                _plxwritereg_1(sc, EEPROM, tmp & 0xF0);
        }
        clock_eeprom(sc);
}

static void
setup_eeprom(struct sis1100_softc* sc,
        int len,        /* # of bits */
        u_int32_t val   /* bit pattern */
        )
{
        u_int32_t epv;  /* base value */
        u_int32_t mx;

        mx=1L <<(len-1);
        deselect_eeprom(sc);
        epv =(_plxreadreg_1(sc, EEPROM) & 0xF0) |EEP_S;            /* select */
        _plxwritereg_1(sc, EEPROM, epv);
        clock_eeprom(sc);

        do {
                if (!(val & mx)) {
                        /* reset D(ata), reset C(lock) */
                        _plxwritereg_1(sc, EEPROM, epv);
                } else {
                        /* set D(ata), reset C(lock) */
                        _plxwritereg_1(sc, EEPROM, epv|EEP_D);
                }

                clock_eeprom(sc);

                mx >>=1;
        } while (mx);
}

static void
setup_eeprom1(struct sis1100_softc* sc, u_int8_t command, u_int8_t address)
{
        u_int32_t val;

        val=(1<<2|command)<<EEP_ALEN|address;
        setup_eeprom(sc, EEP_ALEN+3, val);
}

static void
setup_eeprom2(struct sis1100_softc* sc, u_int8_t command, u_int8_t address,
        u_int16_t data)
{
        u_int32_t val;

        val=((1<<2|command)<<EEP_ALEN|address)<<16|data;
        setup_eeprom(sc, EEP_ALEN+19, val);
}

static u_int16_t
read_eeprom_word(struct sis1100_softc* sc, u_int8_t addr)
{
        u_int32_t tmp;
        u_int16_t mx;
        int i;

        setup_eeprom1(sc, 6, addr);

        /* select, float the EEDO pin for read */
        tmp=_plxreadreg_1(sc, EEPROM);
        _plxwritereg_1(sc, EEPROM, tmp|EEP_D|EEP_S);

        tmp=_plxreadreg_1(sc, EEPROM); /* dummy bit */

        for (i=0, mx=0; i<16; i++) {
                clock_eeprom(sc);
                mx <<=1;
                tmp=_plxreadreg_1(sc, EEPROM);
                if ((tmp & EEP_Q) != 0) mx |=1;

        }

        deselect_eeprom(sc);
        return mx;
}

int
sis1100_read_eeprom(struct sis1100_softc* sc,
        u_int8_t num, u_int8_t addr, u_int16_t* data)
{
        u_int32_t tmp;
        u_int16_t mx;
        int i, n, res=0;

        setup_eeprom1(sc, 6, addr);

        /* select, float the EEDO pin for read */
        tmp=_plxreadreg_1(sc, EEPROM);
        _plxwritereg_1(sc, EEPROM, tmp|EEP_D|EEP_S);

        tmp=_plxreadreg_1(sc, EEPROM); /* dummy bit */
        if (tmp & EEP_Q) {
                deselect_eeprom(sc);
                pINFO(sc, "read_eeprom 0x%x: select error", addr);
                return EIO;
        }

        for (n=0; n<num; n++) {
            for (i=0, mx=0; i<16; i++) {
                    clock_eeprom(sc);
                    mx <<=1;
                    tmp=_plxreadreg_1(sc, EEPROM);
                    if ((tmp & EEP_Q) != 0) mx |=1;

            }
#ifdef __NetBSD__
	    res=susword(data, mx)?EFAULT:0;
#elif __linux__
	    res=-put_user(mx, data);
#endif
            if (res) break;
            data++;
        }

        deselect_eeprom(sc);
        return res;
}

int
sis1100_write_eeprom(struct sis1100_softc* sc,
        u_int8_t num, u_int8_t addr, u_int16_t* data)
{
        u_int32_t tmp;
        u_int16_t mx, omx;
        int n, res=0;

        /* enable write */
        setup_eeprom1(sc, 0, 0xc0);
        deselect_eeprom(sc);

        for (n=0; n<num; n++) {
#ifdef __NetBSD__
            {
            int x=fusword(data);
	    res=(x==-1)?EFAULT:0;
            mx=x;
            }
#elif __linux__
	    res=-get_user(mx, data);
#endif
            if (res) break;

            omx=read_eeprom_word(sc, addr);
            if (omx!=mx) {
                int i=10000;

                setup_eeprom2(sc, 1, addr, mx);
                deselect_eeprom(sc);

                /* select, float the EEDO pin for read */
                for (i=0; i<4; i++) {
                    tmp=_plxreadreg_1(sc, EEPROM);
                    _plxwritereg_1(sc, EEPROM, tmp|EEP_D|EEP_S);
                }

                do {
                    tmp=_plxreadreg_1(sc, EEPROM);
                } while (--i && !(tmp & EEP_Q));
                /*pINFO(sc, "write_eeprom: i=%d", i);*/
                if (!(tmp & EEP_Q)) {
                    pINFO(sc, "write_eeprom: timeout");
                }
                deselect_eeprom(sc);
            }
            data++; addr++;
        }

        /* disable write */
        setup_eeprom1(sc, 0, 0);
        deselect_eeprom(sc);

        return res;
}
