blob: cfabb2eb0dabec1aa81b57a8fd5f887bd83f6eb9 [file] [log] [blame]
# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Allows creation of i2c interface via libftdii2c (C) library for FTDI devices.
"""
import ctypes
import logging
import ftdi_common
import ftdi_utils
import i2c_base
class Fi2cError(Exception):
"""Class for exceptions of Fi2c."""
def __init__(self, msg, value=0):
"""Fi2cError constructor.
Args:
msg: string, message describing error in detail
value: integer, value of error when non-zero status returned. Default=0
"""
super(Fi2cError, self).__init__(msg, value)
self.msg = msg
self.value = value
class Fi2cContext(ctypes.Structure):
"""Defines primary context structure for libftdii2c.
Declared in ftdii2c.h and named fi2c_context.
"""
_fields_ = [
('fc', ctypes.POINTER(ftdi_common.FtdiContext)),
('gpio', ftdi_common.Gpio),
('clk', ctypes.c_uint),
('error', ctypes.c_int),
('slv', ctypes.c_ubyte),
('buf', ctypes.POINTER(ctypes.c_ubyte)),
('bufcnt', ctypes.c_int),
('bufsize', ctypes.c_int),
]
class Fi2c(i2c_base.BaseI2CBus):
"""Provide interface to libftdii2c c-library via python ctypes module.
"""
def __init__(self, vendor=ftdi_common.DEFAULT_VID,
product=ftdi_common.DEFAULT_PID, interface=2, serialname=None):
"""Fi2c constructor.
Loads libraries for libftdi, libftdii2c. Creates instance objects
(Structures), Fi2cContext, FtdiContext and Gpio to iteract with the library
and intializes them.
Args:
vendor: usb vendor id of FTDI device
product: usb product id of FTDI device
interface: interface number of FTDI device to use
serialname: string of device serialname/number as defined in FTDI eeprom.
"""
i2c_base.BaseI2CBus.__init__(self)
self._logger = logging.getLogger('Fi2c')
self._logger.debug('')
(self._flib, self._lib, self._gpiolib) = \
ftdi_utils.load_libs('ftdi', 'ftdii2c', 'ftdigpio')
self._fargs = ftdi_common.FtdiCommonArgs(
vendor_id=vendor, product_id=product, interface=interface,
serialname=serialname)
self._fc = ftdi_common.FtdiContext()
self._fic = Fi2cContext()
self._gpio = ftdi_common.Gpio()
self._is_closed = True
self.init()
self._i2c_mask = ~self._fic.gpio.mask
def __del__(self):
"""Fi2c destructor.
Calls close to release device
"""
if not getattr(self, '_is_closed', True):
self.close()
super(Fi2c, self).__del__()
def open(self):
"""Opens access to FTDI interface as a i2c (MPSSE mode) interface.
Raises:
Fi2cError: If open fails
"""
err = self._lib.fi2c_open(
ctypes.byref(self._fic), ctypes.byref(self._fargs))
if err:
raise Fi2cError('fi2c_open', err)
self._is_closed = False
def close(self):
"""Close connection to FTDI device and cleanup.
Raises:
Fi2cError: If close fails
"""
err = self._lib.fi2c_close(ctypes.byref(self._fic))
if err:
raise Fi2cError('fi2c_close', err)
self._is_closed = True
super(Fi2c, self).close()
def init(self):
"""Initialize i2c interface.
Raises:
Fi2cError: If init fails
"""
super(Fi2c, self).init()
err = self._flib.ftdi_init(ctypes.byref(self._fc))
if err:
raise Fi2cError('ftdi_init', err)
err = self._lib.fi2c_init(ctypes.byref(self._fic), ctypes.byref(self._fc))
if err:
raise Fi2cError('fi2c_init', err)
def setclock(self, speed=100000):
"""Sets i2c clock speed.
Args:
speed: clock speed in hertz. Default is 100kHz
"""
if self._lib.fi2c_setclock(ctypes.byref(self._fic), speed):
raise Fi2cError('fi2c_setclock')
def _raw_wr_rd(self, slv, wlist, rcnt):
"""Write and/or read a slave i2c device.
Args:
slv: 7-bit address of the slave device
wlist: list of bytes to write to the slave. If list length is zero its
just a read
rcnt: number of bytes to read from the device. If zero, its just a write
Returns:
list of c_ubyte's read from i2c device.
"""
self._logger.debug('')
if wlist is None:
wlist = []
if rcnt is None:
rcnt = 0
self._fic.slv = slv
wcnt = len(wlist)
wbuf_type = ctypes.c_ubyte * wcnt
wbuf = wbuf_type()
for i in xrange(wcnt):
wbuf[i] = wlist[i]
rbuf_type = ctypes.c_ubyte * rcnt
rbuf = rbuf_type()
for i, wval in enumerate(wbuf):
self._logger.debug('wbuf[%i] = 0x%02x' % (i, wval))
err = self._lib.fi2c_wr_rd(
ctypes.byref(self._fic), ctypes.byref(wbuf), wcnt, ctypes.byref(rbuf),
rcnt)
if err:
err_str = 'slave:0x%02x wr:%s rcnt:%d err:%s' % (slv, wlist, rcnt, err)
raise Fi2cError('fi2c_wr_rd', err_str)
for i, rval in enumerate(rbuf):
self._logger.debug('rbuf[%i] = 0x%02x' % (i, rval))
return list(rbuf)
def gpio_wr_rd(self, offset, width, dir_val=None, wr_val=None):
"""Write and/or read GPIO controls
I2C interface on FTDI's MPSSE engine requires 3 bits. One for SCL and two
for SDA ( bi-directional ). The remaining 5 bits of the byte interface can
be used as spare GPIO's providing the interface is left in the native (I2C)
mode. This routine facilitates those GPIO's by calling the same GPIO
library as a native GPIO interface but with the interface type set to
'INTERFACE_TYPE_I2C'.
Args:
offset : bit offset of the gpio to read or write
width : integer, number of contiguous bits in gpio to read or write
dir_val : direction value of the gpio. dir_val is interpretted as:
None : read the pins via libftdi's ftdi_read_pins
0 : configure as input
1 : configure as output
wr_val : value to write to the GPIO. Note wr_val is irrelevant if
dir_val = 0
Returns:
integer value from reading the gpio value ( masked & aligned )
Raises:
Fi2cError: if gpio's mask would interfere with i2c's bits
"""
rd_val = ctypes.c_ubyte()
self._gpio.mask = (pow(2, width) - 1) << offset
if self._gpio.mask & self._i2c_mask:
raise Fi2cError('gpio mask violates i2c mask')
if wr_val is not None and dir_val is not None:
self._gpio.direction = self._gpio.mask
self._gpio.value = wr_val << offset
self._gpiolib.fgpio_wr_rd(
ctypes.byref(self._fic), ctypes.byref(self._gpio),
ctypes.byref(rd_val), ftdi_common.INTERFACE_TYPE_I2C)
else:
self._gpiolib.fgpio_wr_rd(
ctypes.byref(self._fic), 0, ctypes.byref(rd_val),
ftdi_common.INTERFACE_TYPE_I2C)
self._logger.debug('mask:0x%x val:%s returned %d' % (self._gpio.mask,
str(wr_val),
rd_val.value))
return (rd_val.value & self._gpio.mask) >> offset
def test():
"""Test code.
(TODO) tbroch: enhance and make Googley & pythonic from a unittest perspective
"""
(options, args) = ftdi_utils.parse_common_args(interface=2)
loglevel = logging.INFO
if options.debug:
loglevel = logging.DEBUG
logging.basicConfig(
level=loglevel,
format='%(asctime)s - %(name)s - ' + '%(levelname)s - %(message)s')
fobj = Fi2c(options.vendor, options.product, options.interface)
fobj.open()
fobj.setclock(100000)
wbuf = [0]
slv = 0x21
rbuf = fobj.wr_rd(slv, wbuf, 1)
logging.info('first: i2c read of slv=0x%02x reg=0x%02x == 0x%02x', slv,
wbuf[0], rbuf[0])
errcnt = 0
for cnt in xrange(1000):
try:
rbuf = fobj.wr_rd(slv, [], 1)
except:
errcnt += 1
logging.error('errs = %d cnt = %d', errcnt, cnt)
logging.info('last: i2c read of slv=0x%02x reg=0x%02x == 0x%02x', slv,
wbuf[0], rbuf[0])
fobj.close()
if __name__ == '__main__':
test()