/* 
   Common Flash Interface probe code.
   (C) 2000 Red Hat. GPL'd.
   $Id: cfi_probe.c,v 1.83 2004/11/16 18:19:02 nico Exp $
*/

#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <asm/io.h>
#include <asm/byteorder.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/interrupt.h>

#include <linux/mtd/xip.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/map.h>
#include <linux/mtd/cfi.h>
#include <linux/mtd/gen_probe.h>

#include <asm/mips-boards/ur8.h>
#include <asm/mach-ur8/hw_spi.h>

/*------------------------------------------------------------------------------------------*\
 * Definitionen SPI-Flash
 * nach einigen Komandos muss CE wieder high sein -> deshalb gleich (x << 24)
 * gesetzt werden diese mit spi_cmd_simple
\*------------------------------------------------------------------------------------------*/
#define WRITE_ENABLE                    (0x06 << 24)       /*--- write enable ---*/
#define WRITE_DISABLE                   (0x04 << 24)       /*--- write disable ---*/
#define READ_ID                         (0x9F << 24)       /*--- read identification ---*/
#define READ_STATUS                     (0x05 << 24)       /*--- read status register ---*/
#define WRITE_STATUS                    (0x01 << 24)       /*--- write status register ---*/
#define READ                            (0x03 << 24)       /*--- read data ---*/
#define FASTREAD                        (0x0B << 24)       /*--- fast read data ---*/
#define PARALLEL_MODE                   (0x55 << 24)       /*--- parallel mode ---*/
#define SECTOR_ERASE                    (0x20 << 24)       /*--- sector erase ---*/ 
#define CHIP_ERASE                      (0x60 << 24)       /*--- chip erase ---*/
#define PAGE_PROGRAM                    (0x02 << 24)       /*--- page program ---*/
#define DEEP_POWER_DOWN                 (0xB9 << 24)       /*--- deep power down ---*/
#define ENTER_4K                        (0xA5 << 24)       /*--- enter 4kb ---*/
#define EXIT_4K                         (0xB5 << 24)       /*--- exit 4kb ---*/
#define RELEASE_POWER_DOWN              (0xAB << 24)       /*--- release from deep power-down ---*/
#define READ_ELECTRONIK_ID  REALEASE_POWER_DOWN            /*--- read electronic id ---*/
#define READ_ELECTRONIC_ID_MANUFACTURE  0x90               /*--- read electronic manufacturer id & device id ---*/

/*--- status bits ---*/ 
#define WIP                 (1 << 0)
#define WEL                 (1 << 1)
#define BP0                 (1 << 2)
#define BP1                 (1 << 3)
#define BP2                 (1 << 4)
#define BP3                 (1 << 5)
#define PROG_ERROR          (1 << 6)
#define REG_WRITE_PROTECT   (1 << 7)

/*--- #define DEBUG_SPI ---*/ 

#if defined(DEBUG_SPI)
#define DBG_SPI(...)      printk(KERN_NOTICE __VA_ARGS__)
#else
#define DBG_SPI(...)  
#endif

#define SPI_BIG_ENDIAN
#define MX_DEVICE_ID_64MB_SPI       0x16      /*--- MX25L6405 ---*/
#define MX_MANUFACTURE_ID           0xC2      
#define SPI_FLASH_BUFFER_SIZE       256

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
struct _spi_register   *const SPI = (struct _spi_register *)&(*(volatile unsigned int *)(UR8_SPI_BASE));
extern struct _hw_gpio *GPIO;

struct mx25l6405 {
	struct semaphore	lock;
	struct mtd_info		mtd;
};


#define SPI_BIG_ENDIAN
/*------------------------------------------------------------------------------------------*\
 * Der UR8 Bootcode liest 32Bit Worte in den internen RAM und führt diese aus.
 * 32Bit Worte kommen in BIG_ENDIAN im Dataregister an - wir schreiben auch BIG_ENDIAN
 * bei 8Bit muss die Adresse entsprechend BIG_ENDIAN berechnet werden
\*------------------------------------------------------------------------------------------*/
void spi_cmd_simple(unsigned int cmd) {

    SPI->data.Register = cmd;
    SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_8 + SPI_CMD_WRITE + 0;    /*--- framelength = 1 ---*/
    while (SPI->status.Bits.busy || !SPI->status.Bits.wc);

}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int spi_read_byte(unsigned int address, unsigned char *pdata) {

#if defined(SPI_BIG_ENDIAN)
    SPI->data.Register = READ + (address ^ 3);
#else
    SPI->data.Register = READ + address;
#endif
    SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_32 + SPI_CMD_WRITE + 1;  /*--- framelength = 2 ---*/
    while (SPI->status.Bits.busy || !SPI->status.Bits.wc);                  /*--- Kommando absetzen ---*/
    SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_8 + SPI_CMD_READ + 1;    /*--- framelength = 2 ---*/
    while (SPI->status.Bits.busy || !SPI->status.Bits.wc);                  /*--- Byte lesen ---*/
    *pdata = SPI->data.Byte[0];
    return 1;
}

/*------------------------------------------------------------------------------------------*\
 * 4 Byte aligned, wir kopieren immer 4 Bytes 
\*------------------------------------------------------------------------------------------*/
unsigned int spi_read_block(unsigned int address, unsigned char *pdata, unsigned int datalen) {

    unsigned int len;
   
    len = datalen = datalen >> 2;

    SPI->data.Register = READ + address;
    SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_32 + SPI_CMD_WRITE + len;    /*--- (len - 1) + 1 ist Framelen ---*/
    while (SPI->status.Bits.busy || !SPI->status.Bits.wc);

    while (len) {
        SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_32 + SPI_CMD_READ + len;
        while (SPI->status.Bits.busy || !SPI->status.Bits.wc);
        /*--- DBG_SPI("[spi_read_block] 0x%x\n", SPI->data); ---*/
#if defined(SPI_BIG_ENDIAN)
        *(unsigned int *)pdata = SPI->data.Register;
        pdata += 4;
#else
        *pdata++ = SPI->data.Byte[3];   /*--- da wir 4 Byte lesen, ist LSB im MSB ---*/
        *pdata++ = SPI->data.Byte[2];
        *pdata++ = SPI->data.Byte[1];
        *pdata++ = SPI->data.Byte[0];
#endif
        len--;
    }

    return datalen << 2;
}

/*------------------------------------------------------------------------------------------*\
 * aus Performacegründen kopieren wir zuerst 32bit weise, den Rest 8bit weise
 * int spi_read(unsigned int address, unsigned char *pdata, unsigned int len) {
\*------------------------------------------------------------------------------------------*/
static int spi_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf) {

    struct map_info *map = (struct map_info *)mtd->priv;
    struct mx25l6405 *flash = (struct mx25l6405 *)map->fldrv_priv;
    unsigned int read, read_len;
    unsigned char *buffer = buf;
    
	down(&flash->lock);

    DBG_SPI("[spi_read] 0x%x len %d 0x%p ", (unsigned int)from, len, buffer);

    *retlen = 0;

    from &= 0xFFFFFF;    /*--- die oberen Adressen sind uninteressant ---*/
    while (*retlen < len) {
        if ((from & 3) || ((unsigned int)buffer & 3) || ((len - *retlen) < 4)) {     /*--- byteweise lesen ---*/
            DBG_SPI("[spi_read] *retlen 0x%x len %d\n", (unsigned int)from, len);
            read = spi_read_byte(from, buffer);
        } else {
            DBG_SPI("[spi_read] Int 0x%x len %d\n", (unsigned int)from, len & ~3);
            read_len = (len - *retlen) > ((SPI_MAX_FRAME_LEN-1)<<2) ? ((SPI_MAX_FRAME_LEN-1)<<2) : (len - *retlen);
            read = spi_read_block(from, buffer, read_len);
        }
        from += read;
        buffer += read;
        *retlen += read;
        if (read == 0) {
            printk(KERN_NOTICE "<ERROR: Flash read aborted %x>\n", (unsigned int)from);
            break;
        }
    }

    DBG_SPI("retlen %d\n", *retlen);
	up(&flash->lock);

    return 0;
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned char spi_read_status(void) {

    SPI->data.Register = READ_STATUS;
    SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_16 + SPI_CMD_WRITE + (0 << 0);    /*--- framelength = 1 ---*/
    while (SPI->status.Bits.busy || !SPI->status.Bits.wc);
    
    return (SPI->data.Register & 0xFF);
}

/*------------------------------------------------------------------------------------------*\
\*------------------------------------------------------------------------------------------*/
unsigned int spi_write_byte(unsigned int address, const unsigned char *pdata) {

    unsigned char status;
    
    spi_cmd_simple(WRITE_ENABLE);
    DBG_SPI("[spi_write_byte] status 0x%x\n", spi_read_status());

#if defined(SPI_BIG_ENDIAN)
    SPI->data.Register = PAGE_PROGRAM + (address ^ 3);
#else
    SPI->data.Register = PAGE_PROGRAM + address;
#endif
    SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_32 + SPI_CMD_WRITE + (1 << 0);    /*--- framelength = 2 ---*/
    while (SPI->status.Bits.busy || !SPI->status.Bits.wc);
    
    DBG_SPI("[spi_write_byte] 0x%x\n", *pdata);
    SPI->data.Register = *pdata << 24;
    SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_8 + SPI_CMD_WRITE + (1 << 0);    /*--- framelength = 2 ---*/
    while (SPI->status.Bits.busy || !SPI->status.Bits.wc);

    while (1) {
        status = spi_read_status();
        if (status & PROG_ERROR) {
            return 0;
        }
        if (!(status & WIP))
            break;
    }
    return 1;
    
}

/*------------------------------------------------------------------------------------------*\
 * wir schreiben hier die Daten immer 32 Bit weise
 * SPI_FLASH_BUFFER_SIZE == 256 (bei unserem Flash)
\*------------------------------------------------------------------------------------------*/
unsigned int spi_write_block(unsigned int address, const unsigned char *data, unsigned int datalen) {

    const unsigned char *pdata = data;
    unsigned int *pAddr = (unsigned int *)address;
    unsigned int SectorSize = 0x10000;
    int i, status = 0;

    if (!SectorSize) {
        printk(KERN_NOTICE "Error: <sector_size == 0 Addr 0x%x>\n", address);
        return 0;
    }

    if (datalen > SPI_FLASH_BUFFER_SIZE) {
        printk(KERN_NOTICE "Error: <len > SPI_FLASH_BUFFER_SIZE>\n");
        return 0;
    }
        
    /*--- wir dürfen nicht über eine Sectorgrenze schreiben ---*/
    if (((unsigned int)pAddr % SectorSize) && (datalen > ((unsigned int)pAddr % SectorSize))) {
        datalen -= (unsigned int)pAddr % SectorSize;
    }

    /*--- nicht über die Grenze des Writebuffers schreiben ---*/
    if (((unsigned int)pAddr % SPI_FLASH_BUFFER_SIZE) && (datalen > (SPI_FLASH_BUFFER_SIZE - ((unsigned int)pAddr % SPI_FLASH_BUFFER_SIZE)))) {
        datalen = (SPI_FLASH_BUFFER_SIZE - ((unsigned int)pAddr % SPI_FLASH_BUFFER_SIZE));
    }

    spi_cmd_simple(WRITE_ENABLE);

    SPI->data.Register = PAGE_PROGRAM + address;
    SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_32 + SPI_CMD_WRITE + (datalen >> 2);
    while (SPI->status.Bits.busy || !SPI->status.Bits.wc);

    for (i=0;i<(datalen>>2);i++) {
        /*--- DBG_SPI("[spi_write_block] 0x%x\n", *(unsigned int *)pdata); ---*/
#if defined(SPI_BIG_ENDIAN)
        SPI->data.Register = *(unsigned int *)pdata;
        pdata += 4;
#else
        SPI->data.Byte[3] = *pdata++;
        SPI->data.Byte[2] = *pdata++;
        SPI->data.Byte[1] = *pdata++;
        SPI->data.Byte[0] = *pdata++;
#endif
        SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_32 + SPI_CMD_WRITE + (datalen >> 2);
        while (SPI->status.Bits.busy || !SPI->status.Bits.wc);
    }

    while (1) {
        status = spi_read_status();
        if (status & PROG_ERROR) {
            spi_cmd_simple(WRITE_DISABLE);
            return 0;
        }
        if (!(status & WIP))
            break;
    }
    DBG_SPI("[spi_write_block] wrote %d\n", datalen);
    return datalen;
}

/*------------------------------------------------------------------------------------------*\
 * int spi_write(unsigned int address, unsigned char *pdata, unsigned int len) {
\*------------------------------------------------------------------------------------------*/
static int spi_write(struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf) {

    struct map_info *map = (struct map_info *)mtd->priv;
    struct mx25l6405 *flash = (struct mx25l6405 *)map->fldrv_priv;
    unsigned int written, write;

	down(&flash->lock);

    *retlen = 0;
    to &= 0xFFFFFF;

    DBG_SPI("[spi_write] to 0x%x len %d buf 0x%p\n", (unsigned int)to, len, buf);
    while (*retlen < len) {
        if ((to & 3) || ((unsigned int)buf & 3) || ((len - *retlen) < 4)) {     /*--- byteweise lesen ---*/
            DBG_SPI("[spi_write] *retlen addr 0x%x\n", (unsigned int)to);
            written = spi_write_byte((unsigned int)to, buf);
        } else {
            write = (len - *retlen) > SPI_FLASH_BUFFER_SIZE ? SPI_FLASH_BUFFER_SIZE : (len - *retlen);
            write &= ~3;
            DBG_SPI("[spi_write] Int 0x%x len %d\n", (unsigned int)to, write);
            written = spi_write_block((unsigned int)to, buf, write);
        }
        to += written;
        buf += written;
        *retlen += written;

        if (written == 0) {
            printk(KERN_NOTICE "<ERROR: Flash write aborted %x>\n", (unsigned int)to);
            break;
        }
    }

	up(&flash->lock);
    return 0;
}

/*------------------------------------------------------------------------------------------*\
 * unsigned int spi_erase_cmd(volatile unsigned int address) 
\*------------------------------------------------------------------------------------------*/
int spi_erase(struct mtd_info *mtd, struct erase_info *instr) {

    struct map_info *map = (struct map_info *)mtd->priv;
    struct mx25l6405 *flash = (struct mx25l6405 *)map->fldrv_priv;
    unsigned char status;
    unsigned int ret = 0;
    unsigned int address = instr->addr;
    unsigned int len = instr->len;
    unsigned int erased = 0;

	down(&flash->lock);

    while (len > erased) {

        spi_cmd_simple(WRITE_ENABLE);   /*--- WRITE_ENABLE muss nicht gelöscht werden ---*/ 

        DBG_SPI("[spi_erase_cmd] 0x%x len %d\n", address, len);
        address &= 0xFFFFFF;
        SPI->data.Register = SECTOR_ERASE + address;
        SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_32 + SPI_CMD_WRITE + (0 << 0);    /*--- framelength = 1 ---*/
        while (SPI->status.Bits.busy || !SPI->status.Bits.wc);

        while (1) {
            status = spi_read_status();
            if (status & PROG_ERROR) {
                ret = 1;
                spi_cmd_simple(WRITE_DISABLE);
                break;
            }
            if (!(status & WIP))
                break;
        }
        address += mtd->erasesize;
        erased  += mtd->erasesize;

    }
        
	up(&flash->lock);

    return ret;
}

/*------------------------------------------------------------------------------------------*\
 * auf dem Demoboard nutzen wir SERIAL_CS1
\*------------------------------------------------------------------------------------------*/
struct mtd_info *init_spi_flash(struct map_info *map) {

    struct mx25l6405 *flash;
    struct mtd_info *mtd = NULL;

#if defined(DEBUG_SPI) 
    SPI->clk_ctrl.Bits.dclk_div = 300;  /*--- zum debuggen etwas langsamer, der Ossi ist nicht so schnell ---*/
#else
    SPI->clk_ctrl.Bits.dclk_div = 2;    /*--- spi -clock max. 3x modulefrequency 60/3 = 20MHz ---*/
#endif
    SPI->clk_ctrl.Bits.enable = 1;

    /*--- write REMS ---*/
    SPI->data.Register = READ_ELECTRONIC_ID_MANUFACTURE << 24;
    SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_32 + SPI_CMD_READ + (1 << 0);    /*--- framelength = 1 ---*/
    while (SPI->status.Bits.busy || !SPI->status.Bits.wc);

    /*--- read ---*/
    SPI->cmd.Register = SPI_USE_CS0 + SPI_WORD_LEN_16 + SPI_CMD_READ + (1 << 0);    /*--- framelength = 1 ---*/
    while (SPI->status.Bits.busy || !SPI->status.Bits.wc);

    DBG_SPI("SPI->data.Register 0x%x\n", SPI->data.Register);
    if (SPI->data.Register == ((MX_MANUFACTURE_ID << 8) + MX_DEVICE_ID_64MB_SPI)) {

        flash = kmalloc(sizeof(*flash), GFP_KERNEL);
        if (!flash) {
            printk(KERN_WARNING "Failed to allocate memory for spi-Flash device\n");
            return NULL;
        }
        memset(flash, 0, sizeof(*flash));
        init_MUTEX(&flash->lock);

        map->fldrv_priv = flash;
        
        mtd = kmalloc(sizeof(*mtd), GFP_KERNEL);
        if (!mtd) {
            printk(KERN_WARNING "Failed to allocate memory for MTD device\n");
            return NULL;
        }
        memset(mtd, 0, sizeof(*mtd));
        mtd->priv = map;
        mtd->type = MTD_SPIFLASH;

        mtd->size = 0x800000;
        mtd->erasesize = 0x10000;
        mtd->numeraseregions = 1;
        mtd->eraseregions = kmalloc(sizeof(struct mtd_erase_region_info) * mtd->numeraseregions, GFP_KERNEL);
        if (!mtd->eraseregions) { 
            printk(KERN_WARNING "Failed to allocate memory for MTD erase region info\n");
            goto setup_err;
        }
        mtd->eraseregions[0].offset = 0;
        mtd->eraseregions[0].erasesize = 0x10000;
        mtd->eraseregions[0].numblocks = 128;

        /* Fill in the default mtd operations */
        mtd->erase   = spi_erase;
        mtd->write   = spi_write;
        mtd->read    = spi_read;
        /*--- mtd->sync    = spi_sync; ---*/
        /*--- mtd->suspend = spi_suspend; ---*/
        /*--- mtd->resume  = spi_resume; ---*/
        mtd->flags   = (MTD_CLEAR_BITS|MTD_ERASEABLE);
        mtd->name    = map->name;

        printk(KERN_NOTICE "[spi-flash] done\n");

        __module_get(THIS_MODULE);
        return mtd;
    }

setup_err:
    if (mtd) {
        if (mtd->eraseregions)
            kfree(mtd->eraseregions);
        kfree(mtd);
    }
    printk("[init_spi] <no flash detected>\n");
    return NULL;
}

/*------------------------------------------------------------------------------------------*\
 * init spi, search spi-flash, fill mtd_info
\*------------------------------------------------------------------------------------------*/
struct mtd_info *init_spi_flash(struct map_info *map); 
struct mtd_info *spi_probe(struct map_info *map) {

	return init_spi_flash(map);
}

static struct mtd_chip_driver spi_chipdrv = {
	.probe		= spi_probe,
	.name		= "spi_probe",
	.module		= THIS_MODULE
};

int __init spi_probe_init(void)
{
	register_mtd_chip_driver(&spi_chipdrv);
	return 0;
}

static void __exit spi_probe_exit(void)
{
	unregister_mtd_chip_driver(&spi_chipdrv);
}

module_init(spi_probe_init);
module_exit(spi_probe_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Haiko Schillert <h.schillert@avm.de>");
MODULE_DESCRIPTION("spi chip  driver for mtd flashs");
