#!   /usr/bin/env   python
#    coding: utf8

# Copyright CERN, 2011
# Author: Matthieu Cattin (CERN)
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org
# Last modifications: 27/4/2012

# Import standard modules
import sys
import time

# Import specific modules
import csr

# Class to access the GN4124 (PCIe bridge) chip.
# It uses the CSR class.

class GN4124OperationError(Exception):
    def __init__(self, msg):
        self.msg = msg
    def __str__(self):
        return ("GN4124: %s" %(self.msg))

class CGN4124:

    # Host registers (BAR C), for DMA items storage on the host side
    HOST_BAR = 0xC
    HOST_DMA_CARRIER_START_ADDR = 0x00
    HOST_DMA_HOST_START_ADDR_L = 0x04
    HOST_DMA_HOST_START_ADDR_H = 0x08
    HOST_DMA_LENGTH = 0x0C
    HOST_DMA_NEXT_ITEM_ADDR_L = 0x10
    HOST_DMA_NEXT_ITEM_ADDR_H = 0x14
    HOST_DMA_ATTRIB = 0x18

    # GN4124 chip registers (BAR 4)
    GN4124_BAR = 0x4
    R_PCI_SYS_CFG = 0x800
    R_CLK_CSR = 0x808
    R_INT_CFG0 = 0x820
    R_GPIO_DIR_MODE = 0xA04
    R_GPIO_INT_MASK = 0xA14
    R_GPIO_INT_MASK_CLR = 0xA18
    R_GPIO_INT_MASK_SET = 0xA1C
    R_GPIO_INT_STATUS = 0xA20
    R_GPIO_INT_TYPE = 0xA24
    R_GPIO_INT_VALUE = 0xA28

    CLK_CSR_DIVOT_MASK = 0x3F0
    INT_CFG0_GPIO = 15
    GPIO_INT_SRC = 8

    # GN4124 core registers (BAR 0)
    R_DMA_CTL = 0x00
    R_DMA_STA = 0x04
    R_DMA_CARRIER_START_ADDR = 0x08
    R_DMA_HOST_START_ADDR_L = 0x0C
    R_DMA_HOST_START_ADDR_H = 0x10
    R_DMA_LENGTH = 0x14
    R_DMA_NEXT_ITEM_ADDR_L = 0x18
    R_DMA_NEXT_ITEM_ADDR_H = 0x1C
    R_DMA_ATTRIB = 0x20

    DMA_CTL_START = 0
    DMA_CTL_ABORT = 1
    DMA_CTL_SWAP = 2
    DMA_STA = ['Idle','Done','Busy','Error','Aborted']
    DMA_ATTRIB_LAST = 0
    DMA_ATTRIB_DIR = 1

    PAGE_SIZE = 4096 # bytes

    def rd_reg(self, bar, addr):
        #print("GN4124:READ: bar:0x%1X reg:0x%08X" % (bar,addr))
        return self.bus.iread(bar, addr, 4)

    def wr_reg(self, bar, addr, value):
        #print("GN4124:WRITE: bar:0x%1X reg:0x%08X data:0x%08X" % (bar,addr,value))
        self.bus.iwrite(bar, addr, 4, value)

    def __init__(self, bus, csr):
        self.bus = bus
        self.dma_csr = csr
        self.dma_item_cnt = 0
        # Get pointer list to host memory pages (allocated by the driver)
        self.pages = self.bus.getplist()
        # Shift by 12 to get the 32-bit physical addresses
        self.pages = [addr << 12 for addr in self.pages]
        # Configure GN4124 to generate interrupt (MSI) on rising edge of GPIO 8
        self.set_interrupt_config()
        # Enable interrupt from gn4124 in the driver
        self.bus.irqena()

    # Set GN4124 local bus frequency
    def set_local_bus_freq(self, freq):
        # freq in MHz
        # LCLK = (25MHz*(DIVFB+1))/(DIVOT+1)
        # DIVFB = 31
        # DIVOT = (800/LCLK)-1
        divot = int(round((800/freq)-1,0))
        #print '[GN4124] DIVOT=%d' % divot
        data = 0xe001f00c + (divot << 4)
        #print '[GN4124] CLK_CSR reg=0x%.8X' % data
        #print '[GN4124] Set local bus freq to %dMHz' % int(round(800/(divot+1),0))
        self.wr_reg(self.GN4124_BAR, self.R_CLK_CSR, data)

    # Get GN4142 local bus frequency
    #   return: frequency in MHz
    def get_local_bus_freq(self):
        reg = self.rd_reg(self.GN4124_BAR, self.R_CLK_CSR)
        divot = ((reg & self.CLK_CSR_DIVOT_MASK)>>4)
        return (800/(divot + 1))

    # Get physical addresses of the host memory pages allocated to GN4124
    def get_physical_addr(self):
        return self.pages

    # Enable interrupt handling in the rawrabbit driver
    def irq_en(self):
        self.bus.irqena()

    # Wait for interrupt
    def wait_irq(self, verbose=False):
        if verbose:
            print("[GN4124] Waiting interrupt...")
        ret = self.bus.irqwait()
        if verbose:
            print("[GN4124] Interrupt occured")
        # re-enable the interrupt
        self.bus.irqena()
        return ret

    # GN4124 RSTOUT33 assert/de-assert cycle
    def rstout33_cycle(self):
        # Assert RSTOUT33 pin
        self.wr_reg(self.GN4124_BAR, self.R_PCI_SYS_CFG, 0x00021040)
        # De-assert RSTOUT33 pin
        self.wr_reg(self.GN4124_BAR, self.R_PCI_SYS_CFG, 0x00025000)

    # GN4124 interrupt configuration
    def set_interrupt_config(self):
        # Set interrupt line from FPGA (GPIO8) as input
        self.wr_reg(self.GN4124_BAR, self.R_GPIO_DIR_MODE, (1<<self.GPIO_INT_SRC))
        # Set interrupt mask for all GPIO except for GPIO8
        self.wr_reg(self.GN4124_BAR, self.R_GPIO_INT_MASK_SET, ~(1<<self.GPIO_INT_SRC))
        # Make sure the interrupt mask is cleared for GPIO8
        self.wr_reg(self.GN4124_BAR, self.R_GPIO_INT_MASK_CLR, (1<<self.GPIO_INT_SRC))
        # Make sure the interrupt is edge sensitive for GPIO8
        self.wr_reg(self.GN4124_BAR, self.R_GPIO_INT_TYPE, 0x0)
        # Interrupt on rising edge of GPIO8
        self.wr_reg(self.GN4124_BAR, self.R_GPIO_INT_VALUE, (1<<self.GPIO_INT_SRC))
        # GPIO as interrupt 0 source
        self.wr_reg(self.GN4124_BAR, self.R_INT_CFG0, (1<<self.INT_CFG0_GPIO))

    def get_interrupt_config(self):
        dir_mode = self.rd_reg(self.GN4124_BAR, self.R_GPIO_DIR_MODE)
        int_mask = self.rd_reg(self.GN4124_BAR, self.R_GPIO_INT_MASK)
        int_type = self.rd_reg(self.GN4124_BAR, self.R_GPIO_INT_TYPE)
        int_value = self.rd_reg(self.GN4124_BAR, self.R_GPIO_INT_VALUE)
        int_cfg = self.rd_reg(self.GN4124_BAR, self.R_INT_CFG0)
        return dir_mode, int_mask, int_type, int_value, int_cfg

    # Get DMA controller status
    def get_dma_status(self):
        reg = self.dma_csr.rd_reg(self.R_DMA_STA)
        if(reg > len(self.DMA_STA)):
            #print("[GN4142] DMA status register : 0x%.8X") % reg
            raise GN4124OperationError("Invalid DMA status")
        else:
            return self.DMA_STA[reg]

    # Configure DMA byte swapping
    #   0 = A1 B2 C3 D4 (straight)
    #   1 = B2 A1 D4 C3 (swap bytes in words)
    #   2 = C3 D4 A1 B2 (swap words)
    #   3 = D4 C3 B2 A1 (invert bytes)
    def set_dma_swap(self, swap):
        if (swap > 3) | (swap < 0):
            raise GN4124OperationError('Invalid swapping configuration : requested %d (should be between 0 and 3)' % swap)
        else:
            self.dma_csr.wr_reg(self.R_DMA_CTL, (swap << self.DMA_CTL_SWAP))

    # Get DMA byte swapping configuration
    def get_dma_swap(self):
        return (0x3 & (self.dma_csr.rd_reg(self.R_DMA_CTL) >> self.DMA_CTL_SWAP))

    # Add DMA item (first item is on the board, the following in the host memory)
    #   carrier_addr, host_addr, length and next_item_addr are in bytes
    #   dma_dir  = 1 -> PCIe to carrier
    #   dma_dir  = 0 -> carrier to PCIe
    #   dma_last = 0 -> last item in the transfer
    #   dma_last = 1 -> more item in the transfer
    #   Limitations:
    #    - Only supports 32-bit host address
    #    - Supports up to 128 items
    def add_dma_item(self, carrier_addr, host_addr, length, dma_dir, last_item):
        #print("GN4124:ADD_DMA_ITEM: item cnt:%d"%self.dma_item_cnt)
        if(0 == self.dma_item_cnt):
            # write the first DMA item in the carrier
            self.dma_csr.wr_reg(self.R_DMA_CARRIER_START_ADDR, carrier_addr)
            self.dma_csr.wr_reg(self.R_DMA_HOST_START_ADDR_L, (host_addr & 0xFFFFFFFF))
            self.dma_csr.wr_reg(self.R_DMA_HOST_START_ADDR_H, (host_addr >> 32))
            self.dma_csr.wr_reg(self.R_DMA_LENGTH, length)
            self.dma_csr.wr_reg(self.R_DMA_NEXT_ITEM_ADDR_L, (self.pages[0] & 0xFFFFFFFF))
            self.dma_csr.wr_reg(self.R_DMA_NEXT_ITEM_ADDR_H, 0x0)
            attrib = (dma_dir << self.DMA_ATTRIB_DIR) + (last_item << self.DMA_ATTRIB_LAST)
            self.dma_csr.wr_reg(self.R_DMA_ATTRIB, attrib)
        else:
            # write nexy DMA item(s) in host memory
            # uses 255 pages (out of 256) to store DMA items
            # current and next item addresses are automatically set
            if (self.dma_item_cnt*0x20) > self.PAGE_SIZE * 255:
                raise GN4124OperationError('Maximum number of DMA items exceeded!')
            current_item_addr = (self.dma_item_cnt-1)*0x20
            next_item_addr = (self.dma_item_cnt)*0x20
            next_item_page = ((self.dma_item_cnt)*0x20)/0x1000
            self.wr_reg(self.HOST_BAR, self.HOST_DMA_CARRIER_START_ADDR + current_item_addr, carrier_addr)
            self.wr_reg(self.HOST_BAR, self.HOST_DMA_HOST_START_ADDR_L + current_item_addr, host_addr)
            self.wr_reg(self.HOST_BAR, self.HOST_DMA_HOST_START_ADDR_H + current_item_addr, 0x0)
            self.wr_reg(self.HOST_BAR, self.HOST_DMA_LENGTH + current_item_addr, length)
            self.wr_reg(self.HOST_BAR, self.HOST_DMA_NEXT_ITEM_ADDR_L + current_item_addr, self.pages[next_item_page] + (next_item_addr & 0xFFF))
            self.wr_reg(self.HOST_BAR, self.HOST_DMA_NEXT_ITEM_ADDR_H + current_item_addr, 0x0)
            attrib = (dma_dir << self.DMA_ATTRIB_DIR) + (last_item << self.DMA_ATTRIB_LAST)
            self.wr_reg(self.HOST_BAR, self.HOST_DMA_ATTRIB + current_item_addr, attrib)
        self.dma_item_cnt += 1

    # Print all DMA items
    def print_dma_items(self):
        print("Host memory page addresses:")
        for page in self.pages:
            print("0x%x"%page)

        print("First item (in FPGA):")
        for addr in range(0x0, 0x24, 0x4):
            print("0x%02X: 0x%08X"%(addr, self.dma_csr.rd_reg(addr)))

        print("Items in host memory:")
        for item in range(self.dma_item_cnt*0x20):
            print("Item %d:"%item)
            for addr in range(0x0, 0x20, 0x4):
                print("0x%02X: 0x%08X"%(item*0x20 + addr, self.rd_reg(self.HOST_BAR, item*0x20 + addr)))

    # Start DMA transfer
    def start_dma(self):
        self.dma_item_cnt = 0
        self.dma_csr.wr_bit(self.R_DMA_CTL, self.DMA_CTL_START, 1)
        # The following lines should be removed
        # when the GN4124 vhdl core will implement auto clear of start bit
        #while(('Idle' == self.get_dma_status()) or
        #      ('Busy' == self.get_dma_status())):
        #    pass
        #self.dma_csr.wr_bit(self.R_DMA_CTL, self.DMA_CTL_START, 0)

    # Abort DMA transfer
    def abort_dma(self):
        self.dma_item_cnt = 0
        self.dma_csr.wr_bit(self.R_DMA_CTL, self.DMA_CTL_ABORT, 1)
        # The following lines should be removed
        # when the GN4124 vhdl core will implement auto clear of abort bit
        #while('Aborted' != self.get_dma_status()):
        #    pass
        self.dma_csr.wr_bit(self.R_DMA_CTL, self.DMA_CTL_ABORT, 0)

    # Get memory page content
    def get_memory_page(self, page_nb):
        data = []
        for i in range(2**10):
            data.append(self.rd_reg(self.HOST_BAR, (page_nb<<12)+(i<<2)))
        return data

    # Set memory page content
    def set_memory_page(self, page_nb, pattern):
        for i in range(2**10):
            self.wr_reg(self.HOST_BAR, (page_nb<<12)+(i<<2), pattern)
