# Author: Theodor Stana <theodor.stana@gmail.com>
# Date: October 2014
#
# 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 3
# 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, you may find one here:
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
# or you may search the http://www.gnu.org website for the version 2 license,
# or you may write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
#

import sys
sys.path.append("../../ei2c")
from ei2c import *
from comm_type import *

class FlashM25P:

    # Constructor
    # comm_params -- communication parameters defining details about how this
    # class should interface to the M25P flash chip
    def __init__(self, *comm_params):
        self.comm = comm_params[0]
        if (comm_params[0] == ELMA_I2C_MULTIBOOT):
            self.elma = comm_params[1]
            self.slot = comm_params[2]
            self.mb_base = comm_params[3]

    # Low-level SPI transfer using communication type defined in the constructor
    #
    # This method handles the OR-ing together of the data bytes and the control
    # byte in FAR.
    #
    # Formatting of data bytes in the FAR register should be done outside this
    # function.
    def spi_transfer(self, nbytes, cs, dat):
        # Communication type is ELMA I2C, therefore use MultiBoot logic on the
        # FPGA to access the flash
        if (self.comm == ELMA_I2C_MULTIBOOT):
            retval = 0
            wval = []

            # control byte, to be shifted left by 24
            ctrl = ((cs << 3) | 0x4) | (nbytes-1)

            # Use appropriate command by type
            #
            # write - we send up to three data bytes in one FAR register write
            # writemregs - we send up to 24 data bytes in eight FAR register writes
            if isinstance(dat,int):
                self.elma.write(self.slot, self.mb_base+0x10, (ctrl << 24) | dat)
            else:
                for i in xrange(len(dat)):
                    wval.append((ctrl << 24) | dat[i])
                self.elma.writemregs(self.slot, self.mb_base+0x10, wval)

            # Read the data and prepare the return value
            while (retval & (1 << 28) == 0):
                retval = self.elma.read(self.slot, self.mb_base+0x10)

            return retval & 0xffffff

    # This function is used to write data bytes to flash. It calls self.spi_transfer
    # and sends the commands to the flash chip. You can follow the logic of this
    # function by looking at the sequence of the write command in [1]
    def write(self, addr, dat):

        # write enable cmd
        self.spi_transfer(1,1,0x06)
        self.spi_transfer(1,0,0)

        # write page cmd
        self.spi_transfer(1,1,0x02)

        # send address in reverse order
        addr = ((addr & 0xff0000) >> 16) | (((addr & 0xff00) >> 8) << 8) | \
            ((addr & 0xff) << 16)
        self.spi_transfer(3,1,addr)

        # Now, massage the data array into 3-byte commands sent in 32-bit
        # registers, in batches of eight registers (writemregs command).
        i = 0
        l = len(dat)
        while (l):
            wval = []
            # If we have more than 24 bytes left in the data array,
            # we can use the full writemregs
            if (int(l/24)):
                for j in xrange(0, 8):
                    wval.append((dat[(i+2)+3*j] << 16) | \
                            (dat[(i+1)+3*j] << 8) | dat[i+3*j])
                self.spi_transfer(3, 1, wval)
                i += 24;
                l -= 24;

            # When we're left to fewer than 24 bytes in the array,
            # try to use one more writemregs with as many regs as possible
            elif (int(l/3)):
                for j in xrange(0, l/3):
                    wval.append((dat[(i+2)+3*j] << 16) | \
                            (dat[(i+1)+3*j] << 8) | dat[i+3*j])
                self.spi_transfer(3,1,wval);
                i += 3 * int(l/3)
                l -= 3 * int(l/3)

            # When we're down to less than three bytes, just use simple one-byte xfers
            elif (l):
                for j in xrange(0, l):
                    self.spi_transfer(1,1,dat[i])
                    i += 1
                    l -= 1

        self.spi_transfer(1,0,0)


    # This function is used to read a number of data bytes from the flash memory
    # It returns the values of the three consecutive flash registers packed into
    # one 24-bit data value in little-endian order
    def read(self, addr, nrbytes):
        ret = []
        self.spi_transfer(1,1,0x0b)

        # send address in reverse order
        addr = ((addr & 0xff0000) >> 16) | (((addr & 0xff00) >> 8) << 8) | \
                ((addr & 0xff) << 16)
        self.spi_transfer(3,1,addr)
        self.spi_transfer(1,1,0)

        # Read bytes in groups of three
        for i in range(nrbytes):
            ret.append(self.spi_transfer(1,1,0))
        self.spi_transfer(1,0,0)

        return ret

    # Send a sector erase command
    def serase(self, addr):
        self.spi_transfer(1,1,0x06)
        self.spi_transfer(1,0,0)
        self.spi_transfer(1,1,0xD8)
        # send address in reverse order
        addr = ((addr & 0xff0000) >> 16) | (((addr & 0xff00) >> 8) << 8) | \
                ((addr & 0xff) << 16)
        self.spi_transfer(3,1,addr)
        self.spi_transfer(1,0,0)

    # Send a block erase command
    def berase(self):
        self.spi_transfer(1,1,0x06)
        self.spi_transfer(1,0,0)
        self.spi_transfer(1,1,0xc7)
        self.spi_transfer(1,0,0)

    # Read status register command
    def rsr(self):
        self.spi_transfer(1,1,0x05)
        ret = self.spi_transfer(1,1,0)
        self.spi_transfer(1,0,0)
        return ret

