kstmApp/src/drvAsynI2C.cpp
author Heinz Junkes <junkes@fhi-berlin.mpg.de>
Mon, 27 Jun 2016 18:08:05 +0200
changeset 3 ec018606c207
permissions -rw-r--r--
add PVs for PhotonSTM

//******************************************************************************
// Copyright (C) 2015 Florian Feldbauer <florian@ep1.ruhr-uni-bochum.de>
//                    - University Mainz, Institute foer nuc
//
// This file is part of drvAsynCan
//
// drvAsynCan 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.
//
// drvAsynCan 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, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
// brief   AsynPortDriver for PANDA Raspberry Pi CAN interface
//
// version 3.0.0; Jul. 29, 2014
//******************************************************************************

//_____ I N C L U D E S _______________________________________________________

// ANSI C includes
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cerrno>
#include <fcntl.h>
#include <linux/i2c-dev.h>
//#include <linux/i2c.h>
#include <sys/ioctl.h>
#include <sys/types.h>
//#include <sys/select.h>
#include <sys/stat.h>
#include <unistd.h>

// EPICS includes
#include <epicsEvent.h>
#include <epicsExport.h>
#include <epicsMutex.h>
#include <epicsString.h>
#include <epicsThread.h>
#include <epicsTime.h>
#include <epicsTimer.h>
#include <epicsTypes.h>
#include <iocsh.h>

// local includes
#include "drvAsynI2C.h"

//_____ D E F I N I T I O N S __________________________________________________

//_____ G L O B A L S __________________________________________________________

//_____ L O C A L S ____________________________________________________________
static epicsTimerId       i2c_timer;
static epicsTimerQueueId  i2c_timerQueue;

//_____ F U N C T I O N S ______________________________________________________

//------------------------------------------------------------------------------
//! @brief       Timeout handler for I2C communication 
//------------------------------------------------------------------------------
static void timeoutHandler( void *ptr ) {
  drvAsynI2C *pi2c = static_cast<drvAsynI2C*>( ptr );
  pi2c->timeout();
}

//------------------------------------------------------------------------------
//! @brief       Called when asyn clients call pasynOctet->read().
//! @param [in]  pasynUser  pasynUser structure that encodes the reason and address.
//! @param [out] value      Address of the string to read.
//! @param [in]  maxChars   Maximum number of characters to read.
//! @param [out] nActual    Number of characters actually read.
//! @param [out] eomReason  Reason that read terminated.
//! @return      in case of no error occured asynSuccess is returned. Otherwise
//!              asynError or asynTimeout is returned. A error message is stored
//!              in pasynUser->errorMessage.
//! @sa          writeOctet
//!
//! Read a byte stream from the I2C bus.
//! To set the correct slave address the pasynOctet->write() call has to be used.
//! If the slave has multiple registers, use the write call to setup slave
//! address and register address, followed by this read call.
///------------------------------------------------------------------------------
asynStatus drvAsynI2C::readOctet( asynUser *pasynUser, char *value, size_t maxChars,
                                  size_t *nActual, int *eomReason ) {
  if( _fd < 0 ) {
    epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
                   "%s: %s disconnected:", portName, _deviceName );
    return asynError;
  }
  if( maxChars <= 0 ) {
    epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
                   "%s: %s maxchars %d Why <=0?", portName, _deviceName, (int)maxChars );
    return asynError;
  }

  int thisRead = 0;
  int nRead = 0;
  bool timerStarted = false;
  asynStatus status = asynSuccess;

  _timeout = false;

  if( eomReason ) *eomReason = 0;

  for(;;) {
    if( !timerStarted && pasynUser->timeout > 0 ) {
      epicsTimerStartDelay( i2c_timer, pasynUser->timeout );
      timerStarted = true;
    }
    thisRead = read( _fd, value, maxChars );
    if( thisRead > 0 ) {
      nRead = thisRead;
      break;
    } else {
      if( thisRead < 0 && ( errno != EWOULDBLOCK )
                       && ( errno != EINTR )
                       && ( errno != EAGAIN ) ) {
        epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
                       "%s: %s read error: %s",
                       portName, _deviceName, strerror( errno ) );
        status = asynError;
        break;
      }
    }
    if( _timeout ) break;
  }
  if( timerStarted ) epicsTimerCancel( i2c_timer );
  if( _timeout && asynSuccess == status ) status = asynTimeout;

/*
  int mytimeout = (int)( pasynUser->timeout * 1.e6 );
  if ( 0 > mytimeout ) {

    nbytes = read( _fd, value, maxChars );
    if ( 0 > nbytes ) {
      epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize, 
                     "Error receiving message from device '%s': %d %s", 
                     _deviceName, errno, strerror( errno ) );
      return asynError;
    }

  } else {

    fd_set fdRead;
    struct timeval t;
    
    // calculate timeout values
    t.tv_sec  = mytimeout / 1000000L;
    t.tv_usec = mytimeout % 1000000L;
    
    FD_ZERO( &fdRead );
    FD_SET( _fd, &fdRead );
    
    // wait until timeout or a message is ready to get read
    int err = select( _fd + 1, &fdRead, NULL, NULL, &t );
    
    // the only one file descriptor is ready for read
    if ( 0 < err ) {
      nbytes = read( _fd, value, maxChars );
      if ( 0 > nbytes ) {
        epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize, 
                       "Error receiving message from device '%s': %d %s", 
                       _deviceName, errno, strerror( errno ) );
        return asynError;
      }
    }
    
    // nothing is ready, timeout occured
    if ( 0 == err ) return asynTimeout;
    if ( 0 > err )  {
      epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize, 
                     "Error receiving message from device '%s': %d %s", 
                     _deviceName, errno, strerror( errno ) );
      return asynError;
    }
  }
*/

  *nActual = nRead;
  if( eomReason && *nActual >= maxChars ) {
    *eomReason = ASYN_EOM_CNT;
  }

  asynPrint( pasynUser, ASYN_TRACEIO_DRIVER, 
             "%s: read %lu bytes from %s, return %s\n",
             portName, (unsigned long)*nActual, _deviceName,
             pasynManager->strStatus( status ) );
  
  return status; 
}

//------------------------------------------------------------------------------
//! @brief       Called when asyn clients call pasynOctet->write().
//! @param [in]  pasynUser  pasynUser structure that encodes the reason and address.
//! @param [in]  value      Address of the string to write.
//! @param [in]  nChars     Number of characters to write.
//! @param [out] nActual    Number of characters actually written.
//! @return      in case of no error occured asynSuccess is returned. Otherwise
//!              asynError or asynTimeout is returned. A error message is stored
//!              in pasynUser->errorMessage.
//!
//! Write a byte stream to the i2c bus. First byte holds the slave address, followed
//! by the acutal data to send.
//! Example: Setting the configuration register of the AD7998 to convert all
//! 8 channels and use the BUSY output:
//! value = 0x20 0x02 0x0ffa
//! Depending on the slave device, multiple write commands can be concatenated
///------------------------------------------------------------------------------
asynStatus drvAsynI2C::writeOctet( asynUser *pasynUser, char const *value, size_t maxChars,
                                   size_t *nActual ){

  int thisWrite = 0;
  bool timerStarted = false;
  asynStatus status = asynSuccess;

  if( _fd < 0 ) {
    epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
                   "%s: %s disconnected:", portName, _deviceName );
    return asynError;
  }
  asynPrint( pasynUser, ASYN_TRACEIO_DRIVER, 
             "drvAsynI2C::writeOctet: trying to send %u bytes\n",
             maxChars );

  if( 0 == maxChars ) {
    *nActual = 0;
    return asynSuccess;
  }

  _timeout = false;

  printf( "drvAsynI2C::writeOctet: sending: ");
  for( size_t viech = 0; viech < maxChars; ++viech ) {
    printf( "0x%02x", value[viech] );
  }
  printf( "\n" );

  int addr = value[0];
  if( addr != _slaveAddress ) {
    // set slave address
   if( ioctl( _fd, I2C_SLAVE, addr ) < 0 ) {
     epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
                    "%s: %s Can't set slave address: %s",
                    portName, _deviceName, strerror( errno ) );
     return asynError;

    _slaveAddress = addr;
   }
  }
  ++value;
  int nleft = maxChars - 1;
  if( 0 < nleft ) {

    if( pasynUser->timeout > 0 ) {
      epicsTimerStartDelay( i2c_timer, pasynUser->timeout );
      timerStarted = true;
    }

    for(;;) {
      thisWrite = write( _fd, value, nleft );
      if ( 0 < thisWrite ) {
        nleft -= thisWrite;
        if( 0 == nleft ) break;
        value += thisWrite;
      }
      if( _timeout || 0 == pasynUser->timeout ) {
        status = asynTimeout;
        break;
      }
      if( 0 > thisWrite && ( errno != EWOULDBLOCK )
                        && ( errno != EINTR )
                        && ( errno != EAGAIN ) ) {
        epicsSnprintf( pasynUser->errorMessage, pasynUser->errorMessageSize,
                       "%s: %s write error: %s",
                       portName, _deviceName, strerror( errno ) );
        status = asynError;
        break;
      }
    }
    if( timerStarted ) epicsTimerCancel( i2c_timer );

  }

  *nActual = maxChars - nleft;

  asynPrint( pasynUser, ASYN_TRACEIO_DRIVER, 
             "%s: wrote %lu bytes to %s, return %s\n",
             portName, (unsigned long)*nActual, _deviceName,
             pasynManager->strStatus( status ) );
  
  return status; 
}

//------------------------------------------------------------------------------
//! @brief       Connect driver to device
//! @param [in]  pasynUser  pasynUser structure that encodes the reason and address.
//! @return      in case of no error occured asynSuccess is returned. Otherwise
//!              asynError or asynTimeout is returned. A error message is stored
//!              in pasynUser->errorMessage.
///------------------------------------------------------------------------------
asynStatus drvAsynI2C::connect( asynUser *pasynUser ) {
  if( _fd >= 0 ) {
    epicsSnprintf( pasynUser->errorMessage,pasynUser->errorMessageSize,
                   "%s: Link to %s already open!", portName, _deviceName );
    return asynError;
  }
  asynPrint( pasynUser, ASYN_TRACEIO_DRIVER,
             "%s: Open connection to %s\n", portName, _deviceName );

  if( ( _fd = open( _deviceName, O_RDWR ) ) < 0 ) {
    epicsSnprintf( pasynUser->errorMessage,pasynUser->errorMessageSize,
                   "%s: Can't open %s: %s", portName, _deviceName, strerror( errno ) );
    return asynError;
  }
  if( ioctl( _fd, I2C_FUNCS, &_i2cfuncs ) < 0 ) {
    epicsSnprintf( pasynUser->errorMessage,pasynUser->errorMessageSize,
                   "%s: Can't get functionality of %s: %s", portName, _deviceName, strerror( errno ) );
    return asynError;
  }
  pasynManager->exceptionConnect( pasynUser );
  return asynSuccess;
}

//------------------------------------------------------------------------------
//! @brief       Disconnect driver from device
//! @param [in]  pasynUser  pasynUser structure that encodes the reason and address.
//! @return      in case of no error occured asynSuccess is returned. Otherwise
//!              asynError or asynTimeout is returned. A error message is stored
//!              in pasynUser->errorMessage.
///------------------------------------------------------------------------------
asynStatus drvAsynI2C::disconnect( asynUser *pasynUser ) {
  asynPrint( pasynUser, ASYN_TRACEIO_DRIVER,
             "%s: disconnect %s\n", portName, _deviceName );
  epicsTimerCancel( i2c_timer );
  if( _fd >= 0 ) {
    close( _fd );
    _fd = -1;
    pasynManager->exceptionDisconnect(pasynUser);
  } 
  return asynSuccess;
}

//------------------------------------------------------------------------------
//! @brief       Standard C'tor.
//! @param [in]  portName The name of the asynPortDriver to be created.
//! @param [in]  ttyName  The name of the device
//------------------------------------------------------------------------------
drvAsynI2C::drvAsynI2C( const char *portName, const char *ttyName ) 
  : asynPortDriver( portName,
                    0, // maxAddr
                    0, // paramTableSize
                    asynCommonMask | asynOctetMask | asynDrvUserMask, // Interface mask
                    asynCommonMask | asynOctetMask,  // Interrupt mask
                    ASYN_CANBLOCK, // asynFlags
                    1,  // Autoconnect
                    0,  // Default priority
                    0 ) // Default stack size
{
  _deviceName = epicsStrDup( ttyName );
  _fd = -1;
  _slaveAddress = 0;
//  if( ( _fd = open( _deviceName, O_RDWR ) ) < 0 ) {
//    std::cerr << "Cannot open port" << std::endl;
//    return;
//  }
//  if( ioctl( _fd, I2C_FUNCS, &_i2cfuncs ) < 0 ) {
//    std::cerr << "Cannont get I2C_FUNCS" << std::endl;
//    return;
//  }
}

// Configuration routines.  Called directly, or from the iocsh function below 
extern "C" {
  //----------------------------------------------------------------------------
  //! @brief       EPICS iocsh callable function to call constructor
  //!              for the drvAsynI2C class.
  //! @param [in]  portName The name of the asyn port driver to be created.
  //! @param [in]  ttyName  The name of the interface 
  //----------------------------------------------------------------------------
  int drvAsynI2CConfigure( const char *portName, const char *ttyName ) {
    if( !portName ) {
      printf( "Port name missing.\n" );
      return -1;
    }
    if( !ttyName ) {
      printf( "TTY name missing.\n" );
      return -1;
    }
    drvAsynI2C* pi2c = new drvAsynI2C( portName, ttyName );
    i2c_timerQueue = epicsTimerQueueAllocate( 1, epicsThreadPriorityScanLow );
    i2c_timer = epicsTimerQueueCreateTimer( i2c_timerQueue, timeoutHandler, pi2c );
    if( !i2c_timer ) {
      printf( "drvAsynI2C: Can't create timer.\n");
      return -1;
    }
    return( asynSuccess );
  }
  static const iocshArg initI2CArg0 = { "portName", iocshArgString };
  static const iocshArg initI2CArg1 = { "ttyName",  iocshArgString };
  static const iocshArg * const initI2CArgs[] = { &initI2CArg0, &initI2CArg1 };
  static const iocshFuncDef initI2CFuncDef = { "drvAsynI2CConfigure", 2, initI2CArgs };
  static void initI2CCallFunc( const iocshArgBuf *args ) {
    drvAsynI2CConfigure( args[0].sval, args[1].sval );
  }

  //----------------------------------------------------------------------------
  //! @brief   Register functions to EPICS
  //----------------------------------------------------------------------------
  void drvAsynI2CRegister( void ) {
    static int firstTime = 1;
    if ( firstTime ) {
      iocshRegister( &initI2CFuncDef, initI2CCallFunc );
      firstTime = 0;
    }
  }
  
  epicsExportRegistrar( drvAsynI2CRegister );
}