#!/usr/bin/python

# Author: Tomasz?
# 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: 1/4/2012

import rr
import struct
import time
import sys

from xwb_gpio import *
from i2c import *
from eeprom_24aa64 import *
from onewire import *
from ds18b20 import *
from cdac5578 import *

class CFmcDioError(Exception):
   def __init__(self, bus_name, addr, msg):
      self.error_msgs={CFmcDio.I2C_ADDR_EEPROM:"EEPROM (24AA64T)",
         CFmcDio.I2C_ADDR_DAC:"DAC (DAC5578)"}
      self.bus_name = bus_name
      self.addr = addr
      self.msg = msg

   def __str__(self):
      if self.bus_name == "FMC":
         return self.msg + " in " + self.bus_name + ". Is the fmc-dio-5chttla correctly inserted into the carrier?"
      elif self.bus_name == "I2C" and self.addr in self.error_msgs.keys():
         return "I2C device %s at addr 0x%x produced the error: %s" %(self.error_msgs[self.addr], self.addr, self.msg)
      else:
         return "Error in "+self.bus_name+" bus. (Addr: "+hex(self.addr)+"): "+self.msg


class CFmcDio:

   BASE_GPIO = 0x200
   BASE_I2C = 0x100
   BASE_ONEWIRE = 0x0
    
   I2C_ADDR_DAC = 0x48
   I2C_ADDR_EEPROM = 0x50

   I2C_PRESCALER = 400

   PIN_PRSNT = 30

   GPIO_LED_TOP = 27
   GPIO_LED_BOTTOM = 28

   DAC_CHANNEL_CORRESP=[0,1,2,7,4,5]

   def __init__(self, bus, base, init_devices):
      """ fmc-dio-5chttla-board presence check, I2C devices response check and OneWire-device funct. init.
          bus = host bus (PCIe, VME, etc...)
          base = base address
          init_devices = If it is True, I2C and OneWire devices are initialized and can then be used
      """
      self.bus = bus
      self.base = base
      self.gpio = CGPIO(bus, base + self.BASE_GPIO)
      if(not self.fmc_present()):
         raise CFmcDioError("FMC", base, "DIOERR00: PRESENT line is not asserted")
      if init_devices:
         try:
            self.i2c = COpenCoresI2C(bus, base + self.BASE_I2C, self.I2C_PRESCALER)
            board_I2C_periph_addr=[self.I2C_ADDR_DAC,self.I2C_ADDR_EEPROM]
            found_I2C_periph_addr=self.i2c.scan()
         except I2CDeviceOperationError as e:
            raise CFmcDioError("I2C", base, "DIOERR01: "+e.msg)
         if not found_I2C_periph_addr:
            raise CFmcDioError("I2C", base, "DIOERR02: No I2C device found")
         for periph_addr in board_I2C_periph_addr:
            if periph_addr not in found_I2C_periph_addr:
               raise CFmcDioError("I2C", periph_addr, "DIOERR03: Device not found. Only "+str(len(found_I2C_periph_addr))+" I2C devices found")
         try:
            self.eeprom = C24AA64(self.i2c, self.I2C_ADDR_EEPROM);
         except I2CDeviceOperationError as e:
            raise CFmcDioError("I2C", self.I2C_ADDR_EEPROM, "DIOERR04: "+e.msg)

         try:
            self.dac = CDAC5578(self.i2c, self.I2C_ADDR_DAC);
         except I2CDeviceOperationError as e:
            raise CFmcDioError("I2C", self.I2C_ADDR_DAC, "DIOERR05: "+e.msg)

         self.onewire = COpenCoresOneWire(self.bus, base + self.BASE_ONEWIRE, 624/2, 124/2)
         self.ds1820 = CDS18B20(self.onewire, 0);
      else:
         self.i2c=None
         self.onewire=None
         self.dac=None
         self.eeprom=None
         self.ds1820=None

   def fmc_present(self):
      """ Return 1 if FMC PRESENT pin is asserted """
      return not self.gpio.inp(self.PIN_PRSNT);

   def set_dir(self, port, d):
      """ Enable (d=1) or disable (d=0) the output of a specified port ([0-4]) """
      self.gpio.outp(port * 4 + 1, not d)

   def set_out(self, port, d):
      """ Set the output value (d) of a specified port ([0-4]) """
      self.gpio.outp(port * 4, d)

   def set_term(self, port, d):
      """ Enable (d=1) or disable (d=0) the termination resistor (50ohm)t of a specified port ([0-4]) """
      self.gpio.outp(port * 4 + 2, d)

   def get_in(self, port):
      """ Return the input value of a specified port ([0-4]) """
      return self.gpio.inp(port * 4)

   def power(self, ins, clock):
      pass

   def set_led(self, led, state):
      """ Changes the state of a specified fmc-dio-5chttla-board LED
          led=[0,1]
          state=[0=off,1=on]
      """
      gpio_leds=[self.GPIO_LED_TOP, self.GPIO_LED_BOTTOM]
      self.gpio.outp(gpio_leds[led],state)
      #print "LED", gpio_leds[led], "set to", state     

   def set_in_threshold(self, port, threshold):
      """ Configures a specified DAC channel (port) with a specified output value (threshold) """
      if self.dac is not None:
         # Saturation is done in order to eliminate residual calculation errors
         if threshold>255:
            threshold>255
         if threshold<0:
            threshold=0
         try:
            self.dac.out(self.DAC_CHANNEL_CORRESP[port], threshold)
         except I2CDeviceOperationError as e:
            raise CFmcDioError("I2C", self.I2C_ADDR_DAC, "DIOERR06: "+e.msg)
      else:
         raise CFmcDioError("I2C", self.base, "DIOERR07: Device not initialized")

   def get_in_threshold(self, port):
      """ Returns the DAC value set in a specified channel (port) """
      if self.dac is not None:
         try:
            dacval=self.dac.rd_out(self.DAC_CHANNEL_CORRESP[port])
         except I2CDeviceOperationError as e:
            raise CFmcDioError("I2C", self.I2C_ADDR_DAC, "DIOERR08: "+e.msg)
         return(dacval)
      else:
         raise CFmcDioError("I2C", self.base, "DIOERR07: Device not initialized")

   def DACvalue2voltage(self, dacval):
      """ Calculate the voltage corresponding to a specific DAC channel digital value """
      return dacval/255*3.3

   def DACvoltage2value(self, volt):
      """ Calculate the DAC channel digital value corresponding to a specific voltage """
      return int(volt/3.3*255+0.5) # +0.5 due to the int() truncation

   def find_port_voltage(self,port,range_min,range_max):
      """ Uses a binary search algorthm to find the voltage (DAC threshold) in a specified board port
          port: number of fmc-dio-5chttla-board port [0-4]
          range_max: high voltage limit of the range in which the threshold is expected to be found
          range_min: low voltage limit
          The range input parameters are specified just to increase the execution speed
      """
      # Convert the range limits provided in volts to the corresponding digital DAC value
      range_min=self.DACvoltage2value(range_min)
      range_max=self.DACvoltage2value(range_max)
      # check if the specified range_min is too restrictive
      self.set_in_threshold(port,range_min)
      time.sleep(0.010) # wait for the DAC output to stabilize
      port_inp=self.get_in(port)
      if port_inp:
         range_low=range_min # correct provided range
      else:
         range_low=-1
      # check if the specified range_max is too restrictive
      self.set_in_threshold(port,range_max)
      time.sleep(0.010) # wait for the DAC output to stabilize
      port_inp=self.get_in(port)
      if not port_inp:
         range_high=range_max # correct provided range
      else:
         range_high=256
      #print "L: {} H: {}".format(range_low,range_high)
      while (range_high-range_low) > 1: # while thresold not found
         range_mid=(range_low+range_high)/2 # integer arithmetics
         self.set_in_threshold(port,range_mid)
         time.sleep(0.010) # wait for the DAC output to stabilize
         port_inp=self.get_in(port)
         #print "DAC[{}]={}".format(range_mid,port_inp)
         if port_inp: # DAC set to a value below the port input voltage
            range_low=range_mid
         else:
            range_high=range_mid
      return self.DACvalue2voltage((range_low+range_high)/2.0)

   def get_unique_id(self):
      """ Get the ID value of the OneWire IC DS1820 consisting in (crc:serial_number:family_code) """
      if self.ds1820:
         try:
            return self.ds1820.read_serial_number()
         except OneWireDeviceOperationError as e:
            raise CFmcDioError("OneWire", self.base, "DIOERR09: "+e.msg)
      else:
         raise CFmcDioError("OneWire", self.base, "DIOERR10: Device not initialized")

   def get_temp(self):
      """ Returns a temperature value in Celsius degrees by means of the OneWire IC DS1820 """
      if self.ds1820:
         try:
            serial_number = self.ds1820.read_serial_number()
            temp=self.ds1820.read_temp(serial_number)
         except OneWireDeviceOperationError as e:
            raise CFmcDioError("OneWire", self.base, "DIOERR11: "+e.msg)
         return temp
      else:
         raise CFmcDioError("OneWire", self.base, "DIOERR10: Device not initialized")


#spec = rr.Gennum()

#dio= CFmcDio(spec, 0x80000);
            
#print("S/N: %x" % dio.get_unique_id())
#print("Board temp: %d degC" % dio.get_temp());
