#!/usr/bin/env python

#   (C) 2008 Jacob Joseph <jacob@jjoseph.org>
#   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, see <http://www.gnu.org/licenses/>.

# 2008-02-22

# An initial attempt to use the libftdi library for the FTDI usb
# interfaces.  (see
# http://www.intra2net.com/de/produkte/opensource/ftdi/)

# This use of ctypes is modeled after an FTD2xx driver posted in a
# forum by Jonathan Roadley-Battin

from ctypes import *

# Enum types aren't directly supported
#enum ftdi_chip_type { TYPE_AM=0, TYPE_BM=1, TYPE_2232C=2 };
(fct_TYPE_AM, fct_TYPE_BM, fct_TYPE_2232C) = map(c_int, range(3))

#enum ftdi_parity_type { NONE=0, ODD=1, EVEN=2, MARK=3, SPACE=4 };
(fpt_NONE, fpt_ODD, fpt_EVEN, fpt_MARK, fpt_SPACE) = map(c_int, range(5))

#enum ftdi_stopbits_type { STOP_BIT_1=0, STOP_BIT_15=1, STOP_BIT_2=2 };
(fst_STOP_BIT_1, fst_STOP_BIT_15, fst_STOP_BIT_2) = map(c_int, range(3))

#enum ftdi_bits_type { BITS_7=7, BITS_8=8 };
(fbt_BITS_7, fbt_BITS_8) = map(c_int, (7,8))

#enum ftdi_mpsse_mode {
#        BITMODE_RESET  = 0x00,
#            BITMODE_BITBANG= 0x01,
#            BITMODE_MPSSE  = 0x02,
#            BITMODE_SYNCBB = 0x04,
#            BITMODE_MCU    = 0x08,
#            BITMODE_OPTO   = 0x10
#        };
(fmm_BITMODE_RESET, fmm_BITMODE_BITBANG, ffm_BITMODE_MPSEE,
 ffm_BITMODE_SYNCBB, ffm_BITMODE_MCU, ffm_BITMODE_OPTO) = (
    0x00, 0x01, 0x02, 0x04, 0x08, 0x10)

#enum ftdi_interface {
#    INTERFACE_ANY = 0,
#    INTERFACE_A   = 1,
#    INTERFACE_B   = 2
#};
(fi_INTERFACE_ANY, fi_INTERFACE_A, fi_INTERFACE_B) = map(c_int, range(3))

# Shifting commands IN MPSSE Mode
MPSSE_WRITE_NEG = 0x01   # Write TDI/DO on negative TCK/SK edge
MPSSE_BITMODE   = 0x02   # Write bits, not bytes
MPSSE_READ_NEG  = 0x04   # Sample TDO/DI on negative TCK/SK edge
MPSSE_LSB       = 0x08   # LSB first
MPSSE_DO_WRITE  = 0x10   # Write TDI/DO
MPSSE_DO_READ   = 0x20   # Read TDO/DI
MPSSE_WRITE_TMS = 0x40   # Write TMS/CS

# FTDI MPSSE commands
SET_BITS_LOW    = 0x80
SET_BITS_HIGH   = 0x82
GET_BITS_LOW    = 0x81
GET_BITS_HIGH   = 0x83
LOOPBACK_START  = 0x84
LOOPBACK_END    = 0x85
TCK_DIVISOR     = 0x86

#/* Value Low */
#/* Value HIGH */ /*rate is 12000000/((1+value)*2) */
#define DIV_VALUE(rate) (rate > 6000000)?0:((6000000/rate -1) > 0xffff)? 0xffff: (6000000/rate -1)

# Commands in MPSSE and Host Emulation Mode
SEND_IMMEDIATE  = 0x87 
WAIT_ON_HIGH    = 0x88
WAIT_ON_LOW     = 0x89

# Commands in Host Emulation Mode
READ_SHORT      = 0x90
READ_EXTENDED   = 0x91
WRITE_SHORT     = 0x92
WRITE_EXTENDED  = 0x93

class usb_dev_handle(Structure):
    pass

# Pointers
c_ubyte_p = POINTER(c_ubyte)
usb_dev_handle_p = POINTER(usb_dev_handle)

class ftdi_context(Structure):
    _fields_ = [
        # USB specific
        ('usb_dev', usb_dev_handle_p), # struct usb_dev_handle *usb_dev;
        ('usb_read_timeout', c_int),
        ('usb_write_timeout', c_int),
        # FTDI specific
        ('type', c_int),               # enum ftdi_chip_type type; 
        ('baudrate', c_int),
        ('bitbang_enabled', c_ubyte),
        ('readbuffer', c_ubyte_p),
        ('readbuffer_offset', c_uint),
        ('readbuffer_remaining', c_uint),
        ('readbuffer_chunksize', c_uint),
        ('writebuffer_chunksize', c_uint),
        # FTDI FT2232C requirements
        ('interface', c_int),
        ('index', c_int),
        ('in_ep', c_int),
        ('out_ep', c_int),
        # 1: (default) Normal bitbang mode, 2: FT2232C SPI bitbang mode
        ('bitbang_mode', c_ubyte),
        ('error_str', c_char_p)]

class ftdi(object):
    """A ctype interface to the libfdi driver."""

    FT = None      # FTDI shared library
    ftdic = None   # context
    
    def __init__(self, description=None, serial=None):
        self.FT = CDLL("libftdi.so")
        self.ftdic = ftdi_context()
        
        self.init()
        self.usb_open(description=description, serial=serial)

    def close(self):
        self.usb_close()
        self.deinit()
        
    def init(self):
        ret = self.FT.ftdi_init(byref(self.ftdic))
        print "init return:", ret
        if ret < 0:
            raise "Error calling init: couldn't allocate read buffer. Returned: %d" % ret
        return ret

    def deinit(self):
        self.FT.ftdi_deinit(byref(self.ftdic))
        return

    def usb_open(self, vendor=0x0403, product=0x6001,
                      description=None, serial=None):
        """Opens the device.  If description and serial are not
        specified, open the first device found."""
        
        ret = self.FT.ftdi_usb_open(byref(self.ftdic),
                                    vendor, product,
                                    description, serial)
        print "usb_open return:", ret
        if ret < 0:
            raise "Error calling usb_open: Returned: %d" % ret
        return ret
            
    def usb_close(self):
        "Opens the first device with a given vendor and product ids."
        ret = self.FT.ftdi_usb_close(byref(self.ftdic))
        print "usb_close return:", ret
        if ret < 0:
            raise "Error calling usb_close:  Returned: %d" % ret
        return ret

    def enable_bitbang(self, bitmask):
        """Enable bitbang mode. High/On value in the bitmask
        configures a line as an output."""
        ret = self.FT.ftdi_enable_bitbang(byref(self.ftdic),
                                          c_ubyte(bitmask))
        if ret < 0:
            raise "Error calling enable_bitbang:  Returned: %d" % ret
        return ret

    def disable_bitbang(self):
        """Disable bitbang mode."""
        ret = self.FT.ftdi_disable_bitbang(byref(self.ftdic))
        if ret < 0:
            raise "Error calling disable_bitbang:  Returned: %d" % ret
        return ret

    def write_data(self, buf, size=None):
        """Writes data (bytes) in chunks to the chip."""

        # passed a python array
        if type(buf) is list:
            size = len(buf)
            Arr = c_int * len(buf)
            cbuf = Arr( *buf)
        elif size is not None:
            cbuf = buf
        else:
            raise "write_data: must specify size if buf is not a python list"

        #from IPython.Shell import IPShellEmbed
        #ipsh = IPShellEmbed()
        #ipsh("ready to write data")
        
        ret = self.FT.ftdi_write_data(byref(self.ftdic),
                                      byref(cbuf),
                                      size)
        if ret < 0:
            raise "Error calling write_data:  Returned: %d" % ret
        return ret

    def read_pins(self):
        """Read pins"""
        pin_state = c_int()
        
        ret = self.FT.ftdi_read_pins(byref(self.ftdic),
                                     byref(pin_state))
        return pin_state

    def set_baudrate(self, rate):
        ret = self.FT.ftdi_set_baudrate(byref(self.ftdic),
                                         rate)
        if ret < 0:
            raise "Error calling write_data:  Returned: %d" % ret
        return ret

    def purge_buffers(self):
        ret = self.FT.ftdi_usb_purge_buffers(byref(self.ftdic))
        if ret < 0:
            raise "Error calling purge_buffers:  Returned: %d" % ret
        return ret
        
    def write_data_set_chunksize(self, chunksize):
        ret = self.FT.ftdi_write_data_set_chunksize(byref(self.ftdic),
                                                    chunksize)
        if ret < 0:
            raise "Error calling write_data_set_chunksize:  Returned: %d" % ret
        return ret

    def write_data_get_chunksize(self):
        chunksize = c_int()
        ret = self.FT.ftdi_write_data_get_chunksize(byref(self.ftdic),
                                                  byref(chunksize))
        if ret < 0:
            raise "Error calling write_data_get_chunksize:  Returned: %d" % ret
        return chunksize

    def set_latency_timer(self, latency):
        ret = self.FT.ftdi_set_latency_timer(byref(self.ftdic),
                                             latency)
        if ret < 0:
            raise "Error calling set_latency_timer:  Returned: %d" % ret
        return ret

    def get_latency_timer(self):
        latency = c_int()
        ret = self.FT.ftdi_get_latency_timer(byref(self.ftdic),
                                             byref(latency))
        if ret < 0:
            raise "Error calling get_latency_timer:  Returned: %d" % ret
        return latency

    
if __name__ == "__main__":
    ft = ftdi()

    #ft.enable_bitbang(0xFF)

    #Arr = c_int * 1000000
    #buf = Arr(*([0xFF, 0x00] * 500000))
    #time.time()
    #ft.write_data(buf, 1000000)
    #time.time()
