/*
 * (C) Copyright 2001
 * Erik Theisen, Wave 7 Optics, etheisen@mindspring.com
 *  and
 * Bill Hunter, Wave 7 Optics, william.hunter@mediaone.net
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */
#include <config.h>
#include <ppcboot.h>
#include "w7o.h"
#include <asm/processor.h>
#include "error.h"


static void fpga_img_write(unsigned long *src, unsigned long len, unsigned short *daddr)
{
    unsigned long i;
    volatile unsigned long val;
    volatile unsigned short *dest = daddr;   /* volatile - bypass optimizer */
    
    for (i = 0; i < len; i++, src++) {
        val = *src;
        *dest = (unsigned short)((val & 0xff000000L) >> 16);
        *dest = (unsigned short)((val & 0x00ff0000L) >> 8);
        *dest = (unsigned short)(val & 0x0000ff00L);
        *dest = (unsigned short)((val & 0x000000ffL) << 8);
    }
    
    /* Terminate programming with 4 C clocks */
    dest = daddr;
    val = *(unsigned short *)dest;
    val = *(unsigned short *)dest;
    val = *(unsigned short *)dest;
    val = *(unsigned short *)dest;
    
}


int fpgaDownload(unsigned char *saddr,
                 unsigned long size,
                 unsigned short *daddr)
{
    int i;                                   /* index, intr disable flag */
    int start;                               /* timer */
    unsigned long greg, grego;               /* GPIO & output register */
    unsigned long length;                    /* image size in words */
    unsigned long *source;                   /* image source addr */
    unsigned short *dest;                    /* destination FPGA addr */
    volatile unsigned short *ndest;          /* temp destination FPGA addr */
    volatile unsigned short val;             /* temp val */
    unsigned long cnfg = GPIO_XCV_CNFG;      /* FPGA CNFG */
    unsigned long eirq = GPIO_XCV_IRQ;
    int retval = -1;                         /* Function return value */

    /* Setup some basic values */
    length = (size / 4) + 1;                 /* size in words, rounding UP
                                                is OK */ 
    source = (unsigned long *)saddr;
    dest = (unsigned short *)daddr;

    /* Get DCR output register */
    grego = in32(IBM405GP_GPIO0_OR);

    /* Reset FPGA */
    grego &= ~GPIO_XCV_PROG;                 /* PROG line low */
    out32(IBM405GP_GPIO0_OR, grego);

    /* Setup timeout timer */
    start = get_timer(0);
    
    /* Wait for FPGA init line */
    while(in32(IBM405GP_GPIO0_IR) & GPIO_XCV_INIT) { /* Wait for INIT line low */
        /* Check for timeout - 100us max, so use 3ms */
        if (get_timer(start) > 3) {
            printf("     failed to start init.\n");
            log_warn(ERR_XINIT0);             /* Don't halt */
            
            /* Reset line stays low */
            goto done;                       /* I like gotos... */
        }
    }
    
    /* Unreset FPGA */
    grego |= GPIO_XCV_PROG;                  /* PROG line high */
    out32(IBM405GP_GPIO0_OR, grego);
        
    /* Wait for FPGA end of init period .  */
    while(!(in32(IBM405GP_GPIO0_IR) & GPIO_XCV_INIT)) { /* Wait for INIT line hi */

        /* Check for timeout */
        if (get_timer(start) > 3) {
            printf("     failed to exit init.\n");
            log_warn(ERR_XINIT1);
            
            /* Reset FPGA */
            grego &= ~GPIO_XCV_PROG;         /* PROG line low */
            out32(IBM405GP_GPIO0_OR, grego);

            goto done;
        }
    }

    /* Now program FPGA ... */
    ndest = dest;
    for (i = 0; i < CONFIG_NUM_FPGAS; i++) {
        /* Toggle IRQ/GPIO */
        greg = mfdcr(CPC0_CR0);              /* get chip ctrl register */
        greg |= eirq;                        /* toggle irq/gpio */
        mtdcr(CPC0_CR0, greg);               /*  ... just do it */

        /* turn on open drain for CNFG */
        greg = in32(IBM405GP_GPIO0_ODR);     /* get open drain register */
        greg |= cnfg;                        /* CNFG open drain */
        out32(IBM405GP_GPIO0_ODR, greg);     /*  .. just do it */

        /* Turn output enable on for CNFG */
        greg = in32(IBM405GP_GPIO0_TCR);     /* get tristate register */
        greg |= cnfg;                        /* CNFG tristate inactive */
        out32(IBM405GP_GPIO0_TCR, greg);     /*  ... just do it */
        
        /* Setup FPGA for programming */
        grego &= ~cnfg;                      /* CONFIG line low */
        out32(IBM405GP_GPIO0_OR, grego);

        /*
         * Program the FPGA
         */
        printf("\n       destination: 0x%lx ", (unsigned long)ndest);
	
        fpga_img_write(source,  length,  (unsigned short *)ndest);

        /* Done programming */
        grego |= cnfg;                       /* CONFIG line high */
        out32(IBM405GP_GPIO0_OR, grego);

        /* Turn output enable OFF for CNFG */
        greg = in32(IBM405GP_GPIO0_TCR);     /* get tristate register */
        greg &= ~cnfg;                       /* CNFG tristate inactive */
        out32(IBM405GP_GPIO0_TCR, greg);     /*  ... just do it */

        /* Toggle IRQ/GPIO */
        greg = mfdcr(CPC0_CR0);              /* get chip ctrl register */
        greg &= ~eirq;                       /* toggle irq/gpio */
        mtdcr(CPC0_CR0, greg);               /*  ... just do it */
        
        ndest = (unsigned short *)((char *)ndest + 0x00100000L); /* XXX - Next FPGA addr */
        cnfg >>= 1;                          /* XXX - Next  */
        eirq >>= 1;
    }

    /* Terminate programming with 4 C clocks */
    ndest = dest;
    for (i = 0; i < CONFIG_NUM_FPGAS; i++) {
        val = *ndest;
        val = *ndest;
        val = *ndest;
        val = *ndest;
        ndest = (unsigned short *)((char *)ndest + 0x00100000L);
    }
    
    /* Setup timer */
    start = get_timer(0);
    
    /* Wait for FPGA end of programming period .  */
    while(!(in32(IBM405GP_GPIO0_IR) & GPIO_XCV_DONE)) { /* Test DONE line low */

        /* Check for timeout */
        if (get_timer(start) > 3) {
            printf("     done failed to come high.\n");
            log_warn(ERR_XDONE1);
            
            /* Reset FPGA */
            grego &= ~GPIO_XCV_PROG;         /* PROG line low */
            out32(IBM405GP_GPIO0_OR, grego);

            goto done;
        }
    }
    
    printf("\n       FPGA load succeeded.\n");
    retval = 0;                              /* Program OK */
    
done:

    return retval;
}

/* FPGA image is stored in flash */
extern flash_info_t    flash_info[];

int init_fpga(void)
{
    unsigned int i,j,ptr;    // these are general purpose
    unsigned char bufchar;  // general purpose charactor
    unsigned char *buf;     // start of image pointer
    unsigned long len;      // length of image
    unsigned char *fn_buf;  // start of file name string
    unsigned int fn_len;    // length of file name string
    unsigned char *xcv_buf;  // pointer to start of fpga bit file image
    unsigned long xcv_len;    // length of fpga bit file image
    unsigned long crc;      // 30bit crc stored in image
    unsigned long calc_crc; // 30bit crc calculated from image
    int retval = -1;

    /* Tell the world what we are doing */
    printf("FPGA:  ");
    
    /*
     * Get address of first sector where the FPGA
     * image is stored.
     */
    buf = (unsigned char *)flash_info[1].start[0];
    
    /*
     * Get the stored image's CRC & length.
     */
    crc = *(unsigned long *)(buf+4);      /* CRC is first long word */
    len = *(unsigned long *)(buf+8);      /* Cimage len is next long word */

    /* Pedantic */
    if ((len < 0x133A4) || (len > 0x80000))
        goto bad_image;

    /*
     * Get the file name pointer and length.
     */
    
    fn_len = (*(unsigned short *)(buf+12) & 0xff);/* filename length
                                                    is next short */
    fn_buf = buf + 14;
    
    /*
     * Get the FPGA image pointer and length length.
     */
    
    xcv_buf = fn_buf + fn_len;    /* pointer to fpga image */
    xcv_len = len - 14 - fn_len;      /* fpga image length */
    
    /* Check for uninitialized FLASH */
    if ((strncmp(buf, "w7o", 3)!=0) || (len > 0x0007ffffL) || (len == 0))
	      goto bad_image;
 
    /*
     * Calculate and Check the image's CRC.
     */
    calc_crc = crc32(0, xcv_buf, xcv_len);
    if (crc != calc_crc) {
        printf("\nfailed - bad CRC\n");
        goto done;
        }
    
    /*  output the file name */    
    printf("file name  : ");    
    for (i=0;i<fn_len;i++) {
        bufchar = fn_buf[+i];
        if (bufchar<' ' || bufchar>'~') bufchar = '.';
        putc(bufchar);
    }
    
    /* 
     * find rest of display data 
     */
    ptr = 15; // offset to ncd filename length in fpga image
    j= xcv_buf[ptr];  // get length of ncd filename
    if (j>32) goto bad_image; 
    ptr=ptr+j+3;           // skip ncd filename string + 3 bytes more bytes
    /* 
     * output target device string 
     */
    j= xcv_buf[ptr++]-1;  // length of target string less terminator
    if (j>32) goto bad_image;
    printf("\n       target     : ");    
    for (i=0;i<j;i++){
	 bufchar=(xcv_buf[ptr++]);
	 if (bufchar<' ' || bufchar>'~') bufchar = '.';
	 putc(bufchar);
    }
    /* 
     * output compilation date string and time string
     */
    ptr+=3; // skip two bytes of unknown meaning
    printf("\n       synth time : ");    
    j=(xcv_buf[ptr++] - 1); // length of date string less terminator
    if (j>32) goto bad_image;
    for (i=0;i<j;i++){
	 bufchar=(xcv_buf[ptr++]);
	 if (bufchar<' ' || bufchar>'~') bufchar = '.';
	 putc(bufchar);
    }
    ptr+=3; // skip two bytes of unknown meaning
    printf(" - ");    
    j=(xcv_buf[ptr++] - 1); // slen = targ dev string length
    if (j>32) goto bad_image;
    for (i=0;i<j;i++){
	 bufchar=(xcv_buf[ptr++]);
	 if (bufchar<' ' || bufchar>'~') bufchar = '.';
	 putc(bufchar);
    }
    /* 
     * output crc and length strings
     */
    printf("\n       len & crc  : 0x%lx  0x%lx",len,crc);

    
     /*
     * Program the FPGA.
     */
    retval = fpgaDownload((unsigned char*)xcv_buf, xcv_len,
                          (unsigned short *)0xfd000000L);
    return retval;

bad_image:
    printf("\n       BAD FPGA image format @ %lx\n", flash_info[1].start[0]);
    log_warn(ERR_XIMAGE);
done:
    return retval;
}

