#!/usr/bin/python

# Copyright CERN, 2011
# Author: Matthieu Cattin (CERN)
# Author (modifications): Richard Carrillo <rcarrillo(AT)sevensols.com>
# Licence: GPL v2 or later.
# Website: http://www.ohwr.org
# Website: http://www.sevensols.com
# Last modifications: 4/5/2012

# Import standard modules
import sys
import time

# Import specific modules


# Class to access the wishbone to I2C master module from OpenCores
# http://opencores.org/project,i2c

class I2CDeviceOperationError(Exception):
    def __init__(self, addr, msg):
        self.msg = msg
        self.addr = addr
    def __str__(self):
        return ("I2C [Wishbone core base address:0x%08X]: %s" %(self.addr, self.msg))

class COpenCoresI2C:
    # OpenCores I2C registers description
    R_PREL = 0x0
    R_PREH = 0x4
    R_CTR = 0x8
    R_TXR = 0xC
    R_RXR = 0xC
    R_CR = 0x10
    R_SR = 0x10

    CTR_EN = (1<<7)

    CR_STA = (1<<7)
    CR_STO = (1<<6)
    CR_RD = (1<<5)
    CR_WR = (1<<4)
    CR_ACK = (1<<3)

    SR_RXACK = (1<<7)
    SR_TIP = (1<<1)

    # Constant declaration
    WAIT_TIME_OUT = 2


    def wr_reg(self, addr, val):
        self.bus.iwrite(0, self.base_addr +  addr, 4, val)

    def rd_reg(self,addr):
        return self.bus.iread(0, self.base_addr + addr, 4)

    # Initialise the I2C master module
    #   bus = host bus (PCIe, VME, etc...)
    #   base_addr = I2C core base address
    #   prescaler = SCK prescaler, prescaler = (Fsys/(5*Fsck))-1
    def __init__(self, bus, base_addr, prescaler):
        self.bus = bus
        self.base_addr = base_addr
        self.wr_reg(self.R_CTR, 0)
        #print "[I2C] Prescaler: %.4X" % prescaler
        self.wr_reg(self.R_PREL, (prescaler & 0xff))
        #print "[I2C] PREL: %.2X" % self.rd_reg(self.R_PREL)
        self.wr_reg(self.R_PREH, (prescaler >> 8))
        #print "[I2C] PREH: %.2X" % self.rd_reg(self.R_PREH)
        self.wr_reg(self.R_CTR, self.CTR_EN)
        #print "[I2C] CTR: %.2X" % self.rd_reg(self.R_CTR)
        if not(self.rd_reg(self.R_CTR) & self.CTR_EN):
            raise I2CDeviceOperationError(self.base_addr, "Core at base address 0x%08X is not enabled" % self.base_addr)

    def wait_busy(self):
        init_time=time.time()
        while(self.rd_reg(self.R_SR) & self.SR_TIP):
            if (time.time()-init_time) > self.WAIT_TIME_OUT:
                raise I2CDeviceOperationError(self.base_addr, "Wait timeout")

    def start(self, addr, write_mode):
        #print '[I2C] START addr=%.2X'%addr
        addr = addr << 1
        #print '[I2C] addr=%.2X'%addr
        if(write_mode == False):
            addr = addr | 1
        #print '[I2C] addr=%.2X'%addr
        self.wr_reg(self.R_TXR, addr)
        #print '[I2C] R_TXR: %.2X' % self.rd_reg(self.R_TXR)
       	self.wr_reg(self.R_CR, self.CR_STA | self.CR_WR)
       	self.wait_busy()

       	if self.rd_reg(self.R_SR) & self.SR_RXACK:
            raise I2CDeviceOperationError(self.base_addr, "ACK not received after start from device at address 0x%02X" % (addr >> 1))

    def write(self, data, last):
        self.wr_reg(self.R_TXR, data)
        cmd = self.CR_WR
        if(last):
            cmd = cmd | self.CR_STO
        self.wr_reg(self.R_CR, cmd)
        self.wait_busy()
        if(self.rd_reg(self.R_SR) & self.SR_RXACK):
            raise I2CDeviceOperationError(self.base_addr, "No ACK upon write")

    def read(self, last):
        cmd = self.CR_RD
        if(last):
            cmd = cmd | self.CR_STO | self.CR_ACK
        self.wr_reg(self.R_CR, cmd)
        self.wait_busy()

        return self.rd_reg(self.R_RXR)

    def scan(self):
        periph_addr = []
        print "Addresses of I2C devices found:",
        for i in range(0, 128):
            addr = i << 1
            addr |= 1 # Address's LSB is the R/W flag, set to 1 for read operation
            #print '[I2C] addr:%02X' % addr
            self.wr_reg(self.R_TXR, addr)
            self.wr_reg(self.R_CR, self.CR_STA | self.CR_WR)
            self.wait_busy()
            if(not(self.rd_reg(self.R_SR) & self.SR_RXACK)):
                periph_addr.append(i)
                print("0x{:X}".format(i)),
                sys.stdout.flush()
                self.wr_reg(self.R_TXR, 0)
                self.wr_reg(self.R_CR, self.CR_STO | self.CR_WR)
                self.wait_busy()
        print ""
        return periph_addr


##########################################
# Usage example
#gennum = rr.Gennum();
#i2c = COpenCoresI2C(gennum, 0x80000, 500);
