/**
 * @file avmio.c
 *
 * AvmIO - Common functions for i/o based controllers B1 and T1.
 *
 * Copyright: 2000-2003 Thomas Wintergerst. All rights reserved.
 *
 * $FreeBSD$
 * $Id: avmio.c,v 1.1.2.1 2006/02/28 21:35:10 thomas Exp $
 * Project  CAPI for BSD
 * Target   avmaic - CAPI manager driver for AVM active ISDN controllers
 * @date    01.01.2000
 * @author  "Thomas Wintergerst" <twinterg@gmx.de>
 * <p>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * </p>
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

/* system includes */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/errno.h>
#include <machine/bus.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <machine/resource.h>
#include <machine/clock.h>

/* import includes */
#include <c4b/kcapimgr/capi_drv.h>

#define __AVMIO__

/* local includes */
#include <c4b/avmaic/avmaic_misc.h>
#include <c4b/avmaic/avmio.h>





/* === public definitions ================================================ */





/* === private definitions =============================================== */





/* === prototypes for private functions ================================== */





/* === definition of public functions ==================================== */





/**
 * Get byte of data from the controller receive buffer with timeout.
 *
 * @param t                     I: Tag for i/o operations.
 * @param h                     I: Handle for i/o operations.
 * @param piTimeout             I/O: Timeout for the read operation in ticks
 *                                 (<seconds> * HZ). On function return the
 *                                 value will be set to the time remaining
 *                                 (less than null if timeout occurred). This
 *                                 can be used to maintain a single timeout
 *                                 over a couple of read operations.
 *
 * @return                      The value read from the receive buffer.
 */

unsigned char avmio_get_byte
   (bus_space_tag_t     t,
    bus_space_handle_t  h,
    int                *piTimeout)
{
   struct timeval tvStart;
   struct timeval tvCurr;
   int            iCurrTime;
   
   getmicrotime (&tvStart);
   iCurrTime = 0;
   while (! avmio_rx_full (t, h) && *piTimeout - iCurrTime > 0)
   {
      getmicrotime (&tvCurr);
      timevalsub (&tvCurr, &tvStart);
      iCurrTime = tvCurr.tv_usec * hz / 1000000 + tvCurr.tv_sec * hz;
   }
   *piTimeout -= iCurrTime;
   if (*piTimeout <= 0 && ! avmio_rx_full (t, h))
   {
      DBG0 (LOG_ERROR,
            "Tag 0x%03X, handle 0x%03X: Timeout waiting for filled receive buffer (%d ticks, %d available)",
            (unsigned) t, (unsigned) h, iCurrTime, *piTimeout + iCurrTime);
      return (0);
   }
   return (avmio_inp (t, h, AVMAIC_REG_READ));
} /* avmio_get_byte */





/**
 * Write a byte of data to the controller send buffer with timeout.
 *
 * @param t                     I: Tag for i/o operations.
 * @param h                     I: Handle for i/o operations.
 * @param ucVal                 I: The value to write.
 * @param piTimeout             I/O: Timeout for the write operation in ticks
 *                                 (<seconds> * HZ). On function return the
 *                                 value will be set to the time remaining
 *                                 (less than null if timeout occurred). This
 *                                 can be used to maintain a single timeout
 *                                 over a couple of write operations.
 *
 * @retval 0                    The write operation was successful.
 * @retval -1                   The write operation failed because of a timeout.
 */

int avmio_put_byte
   (bus_space_tag_t     t,
    bus_space_handle_t  h,
    unsigned char       ucVal,
    int                *piTimeout)
{
   struct timeval tvStart;
   struct timeval tvCurr;
   int            iCurrTime;
   
   /* wait for empty send buffer */
   getmicrotime (&tvStart);
   iCurrTime = 0;
   while (! avmio_tx_empty (t, h) && *piTimeout - iCurrTime > 0)
   {
      getmicrotime (&tvCurr);
      timevalsub (&tvCurr, &tvStart);
      iCurrTime = tvCurr.tv_usec * hz / 1000000 + tvCurr.tv_sec * hz;
   }
   *piTimeout -= iCurrTime;
   if (*piTimeout <= 0 && ! avmio_tx_empty (t, h))
   {
      DBG0 (LOG_ERROR,
            "Tag 0x%03X, handle 0x%03X: Timeout waiting for empty send buffer (%d ticks, %d available)",
            (unsigned) t, (unsigned) h, iCurrTime, *piTimeout + iCurrTime);
      return (-1);
   }

   bus_space_write_1 (t, h, AVMAIC_REG_WRITE, ucVal);

   return (0);
} /* avmio_put_byte */





/**
 * Write a byte of data to the controller send buffer.
 *
 * This function works nearly the same way as avmio_put_byte(). The only
 * difference is that this function not just writes the byte, but also reads
 * the analyze register of the controller after the write operation.
 *
 * The timeout for the read operation is only maintained internally. So there
 * is no support for one single timeout for multiple consecutive read
 * operations. Instead we use a timeout value of 250ms. This somewhat large
 * timeout value takes into account, that this function is normally called for
 * the first byte (command code) of main operations like CAPI_REGISTER or
 * CAPI_PUT_MESSAGE. When sending these messages to the board, it may still be
 * busy with handling the message sent just before. So we give the board the
 * time needed to empty its message receive buffer. If the board were unable to
 * accept another message, it would have reported this through a special
 * message (AVMAIC_RECEIVE_STOP) and we would be in a busy state, not accepting
 * any new message.
 *
 * @param t                     I: Tag for i/o operations.
 * @param h                     I: Handle for i/o operations.
 * @param ucVal                 I: The value to write.
 *
 * @retval 0                    The write operation was successful.
 * @retval -1                   The write operation failed because of a timeout.
 */

int avmio_safe_put_byte
   (bus_space_tag_t    t,
    bus_space_handle_t h,
    unsigned char      ucVal)
{
   struct timeval tvStart;
   struct timeval tvCurr;
   int            iCurrTime;
   
   /* wait for empty send buffer */
   getmicrotime (&tvStart);
   iCurrTime = 0;
   while (! avmio_tx_empty (t, h) && iCurrTime <= AVMAIC_MAX_IO_WAIT_COMMAND)
   {
      getmicrotime (&tvCurr);
      timevalsub (&tvCurr, &tvStart);
      iCurrTime = tvCurr.tv_usec * hz / 1000000 + tvCurr.tv_sec * hz;
   }
   if (iCurrTime > AVMAIC_MAX_IO_WAIT_COMMAND && ! avmio_tx_empty (t, h))
   {
      DBG0 (LOG_ERROR,
            "Tag 0x%03X, handle 0x%03X: Timeout waiting for empty send buffer (%d ticks)",
            (unsigned) t, (unsigned) h, iCurrTime);
      return (-1);
   }

   (void) avmio_outp (t, h, AVMAIC_REG_WRITE, ucVal);
   return (0);
} /* avmio_safe_put_byte */





/**
 * Get 32-bit word of data from the controller receive buffer.
 *
 * @param t                     I: Tag for i/o operations.
 * @param h                     I: Handle for i/o operations.
 *
 * @return                       The date value read from the controller.
 */

unsigned int avmio_get_word
   (bus_space_tag_t    t,
    bus_space_handle_t h)
{
   int          iTimeout = 4 * AVMAIC_MAX_IO_WAIT_BYTE;
   unsigned int uVal;

   uVal = avmio_get_byte (t, h, &iTimeout);
   uVal |= (avmio_get_byte (t, h, &iTimeout) << 8);
   uVal |= (avmio_get_byte (t, h, &iTimeout) << 16);
   uVal |= (avmio_get_byte (t, h, &iTimeout) << 24);
   
   if (iTimeout <= 0)
   {
      DBG0 (LOG_ERROR,
            "Tag 0x%03X, handle 0x%03X: Timeout reading 32bit word (%d ticks)",
            (int) t, (int) h, 4 * AVMAIC_MAX_IO_WAIT_BYTE);
   }

   return (uVal);
} /* avmio_get_word */





/**
 * Write 32-bit word of data to controller.
 *
 * @param t                     I: Tag for i/o operations.
 * @param h                     I: Handle for i/o operations.
 * @param uVal                  I: The data value to write to the controller.
 *
 * @retval 0                    The write operation was successful.
 * @retval -1                   The write operation failed because of a timeout.
 */

int avmio_put_word
   (bus_space_tag_t    t,
    bus_space_handle_t h,
    unsigned int       uVal)
{
   int iTimeout = 4 * AVMAIC_MAX_IO_WAIT_BYTE;
   
   if (avmio_put_byte (t, h, uVal & 0xFF, &iTimeout) == -1 ||
       avmio_put_byte (t, h, (uVal >> 8) & 0xFF, &iTimeout) == -1 ||
       avmio_put_byte (t, h, (uVal >> 16) & 0xFF, &iTimeout) == -1 ||
       avmio_put_byte (t, h, (uVal >> 24) & 0xFF, &iTimeout) == -1)
   {
      if (iTimeout <= 0)
      {
         DBG0 (LOG_ERROR,
               "Tag 0x%03X, handle 0x%03X: Timeout writing 32bit word (%d ticks)",
               (int) t, (int) h, 4 * AVMAIC_MAX_IO_WAIT_BYTE);
      }
      return (-1);
   }
   return (0);
} /* avmio_put_word */





/**
 * Read data from internal controller register.
 *
 * @param t                     I: Tag for i/o operations.
 * @param h                     I: Handle for i/o operations.
 * @param iReg                  I: The internal register number to read from.
 *
 * @return                      The value read from the controller register.
 */

unsigned int avmio_read_reg
   (bus_space_tag_t    t,
    bus_space_handle_t h,
    int                iReg)
{
   (void) avmio_safe_put_byte (t, h, AVMAIC_READ_INTREG);
   (void) avmio_put_word (t, h, (unsigned int) iReg);
   return (avmio_get_word (t, h));
} /* avmio_read_reg */





/**
 * Write data to internal controller register.
 *
 * @param t                     I: Tag for i/o operations.
 * @param h                     I: Handle for i/o operations.
 * @param iReg                  I: The internal register number to write to.
 * @param uVal                  I: The value to write.
 *
 * @return Nothing.
 */

void avmio_write_reg
   (bus_space_tag_t    t,
    bus_space_handle_t h,
    int                iReg,
    unsigned int       uVal)
{
   (void) avmio_safe_put_byte (t, h, AVMAIC_WRITE_INTREG);
   (void) avmio_put_word (t, h, (unsigned int) iReg);
   (void) avmio_put_word (t, h, uVal);
   
} /* avmio_write_reg */





/**
 * Get slice of data from controller.
 *
 * @param t                     I: Tag for i/o operations.
 * @param h                     I: Handle for i/o operations.
 * @param paucBuf               O: Address of the destination buffer for the
 *                                 slice data.
 * @param nBufLen               I: Length of the destination buffer at paucBuf.
 *
 * @return                      The length of the slice read. This may be larger
 *                              than the buffer size specified in nBufLen. The
 *                              bytes that did not fit into the buffer were
 *                              discarded.
 */

size_t avmio_get_slice
   (bus_space_tag_t     t,
    bus_space_handle_t  h,
    unsigned char      *paucBuf,
    size_t              nBufLen)
{
   int          iTimeout = AVMAIC_MAX_IO_WAIT_SLICE;
   unsigned int uLen;
   int          i;

   /* read length of pending data */
   uLen = avmio_get_word (t, h);

   /* if the destination buffer is not large enough, first fill only the buffer
    */
   if (uLen <= nBufLen)
   {
      i = uLen;
   }
   else
   {
      i = nBufLen;
   }
   while (i-- > 0)
   {
      *paucBuf++ = avmio_get_byte (t, h, &iTimeout);
   }

   /* if the destination buffer is too short, the remaining bytes are read from
    * the controller but discarded
    */
   if (uLen > nBufLen)
   {
      i = uLen - nBufLen;
      while (i-- > 0)
      {
         (void) avmio_get_byte (t, h, &iTimeout);
      }
   }

   /* return the real length of the slice */
   return (uLen);
} /* avmio_get_slice */





/**
 * Reset an i/o based board.
 *
 * @param t                     I: Tag for i/o operations.
 * @param h                     I: Handle for i/o operations.
 *
 * @return Nothing.
 */

void avmio_reset
   (bus_space_tag_t    t,
    bus_space_handle_t h)
{
   /* set reset register to defined (normal) state */
   (void) avmio_outp (t, h, AVMAIC_REG_RESET, 0);

   /* wait for 100 ms */
   DELAY (100 * 1000);

   /* trigger reset flag */
   (void) avmio_outp (t, h, AVMAIC_REG_RESET, 1);

   /* wait for 100 ms */
   DELAY (100 * 1000);

   /* set reset flag back to normal state */
   (void) avmio_outp (t, h, AVMAIC_REG_RESET, 0);

   /* wait for 100 ms */
   DELAY (100 * 1000);

} /* avmio_reset */





/**
 * Get test bit in card.
 *
 * @param t                     I: Tag for i/o operations.
 * @param h                     I: Handle for i/o operations.
 *
 * @retval 0                    Test bit is not set.
 * @retval !0                   Test bit is set.
 */

int avmio_get_test_bit
   (bus_space_tag_t    t,
    bus_space_handle_t h,
    AvmAicCardType_t   cardType)
{
   return
      ((avmio_read_reg
           (t, h,
            (cardType == AVMAIC_CARD_TYPE_M1)
               ? AVMAIC_INTREG_M1_STAT0 : AVMAIC_INTREG_STAT0) & 0x01) != 0);
} /* avmio_get_test_bit */





/**
 * Set test bit in card.
 *
 * @param t                     I: Tag for i/o operations.
 * @param h                     I: Handle for i/o operations.
 * @param fOn                   I:
 *                              - 0
 *                                 Set test bit.
 *                              - !0
 *                                 Reset test bit.
 *
 * @return Nothing.
 */

void avmio_set_test_bit
   (bus_space_tag_t    t,
    bus_space_handle_t h,
    int                fOn,
    AvmAicCardType_t   cardType)
{
   avmio_write_reg (t, h,
                    (cardType == AVMAIC_CARD_TYPE_M1)
                       ? AVMAIC_INTREG_M1_STAT0 : AVMAIC_INTREG_STAT0,
                    fOn ? 0x21 : 0x20);
} /* avmio_set_test_bit */





/*
        register a CAPI application at a controller
        -------------------------------------------
        Note: The controller is downloaded and active when calling this
              function.
*/

unsigned avmio_app_register
   (AvmAicSc_t       *pSc,
    AvmAicPortData_t *pPortData,
    unsigned          uMaxLogicalConnections,
    unsigned          uMaxBDataBlocks,
    unsigned          uMaxBDataLen,
    unsigned          uApplID)
{
   bus_space_tag_t    t = pSc->resInfo.ioTag;
   bus_space_handle_t h = pSc->resInfo.ioHandle;
   unsigned           uSizeMsgBuf;
   unsigned           uRes;
   
   /* send the registration data to the controller */
   uSizeMsgBuf = (uMaxLogicalConnections + 1) * 1024;
   uRes = CAPI_OK;
   if (avmio_safe_put_byte (t, h, AVMAIC_SEND_REGISTER) < 0)
   {
      DBG (LOG_ERROR, pSc->iUnit, "Unable to send %s", "AVMAIC_SEND_REGISTER");
      return (CME_BUSY);
   }
   if (avmio_put_word (t, h, uApplID) < 0 ||
       avmio_put_word (t, h, uSizeMsgBuf) < 0 ||
       avmio_put_word (t, h, uMaxLogicalConnections) < 0 ||
       avmio_put_word (t, h, uMaxBDataBlocks) < 0 ||
       avmio_put_word (t, h, uMaxBDataLen) < 0)
   {
      uRes = CME_OS_RESOURCE_ERROR;
   }
   
   if (uRes != CAPI_OK)
   {
      DBG (LOG_ERROR, pSc->iUnit, "Error 0x%04X registering app. id %u",
	   uRes, uApplID);
   }

   return (uRes);
} /* avmio_app_register */





/*
        release a CAPI application at a controller
        ------------------------------------------
*/

unsigned avmio_app_release
   (AvmAicSc_t       *pSc,
    AvmAicPortData_t *pPortData,
    unsigned          uApplID)
{
   bus_space_tag_t    t = pSc->resInfo.ioTag;
   bus_space_handle_t h = pSc->resInfo.ioHandle;
   unsigned           uRes;
   
   /* prepare to wait for the result of the release request */
   pSc->uRc = 0xFFFFFFFF;
   pSc->fWaitingForRc = 1;
   
   /* send the release request to the controller */
   if (avmio_safe_put_byte (t, h, AVMAIC_SEND_RELEASE) < 0)
   {
      pSc->fWaitingForRc = 0;
      DBG (LOG_ERROR, pSc->iUnit, "unable to send %s", "AVMAIC_SEND_RELEASE");
      return (CME_BUSY);
   }
   if (avmio_put_word (t, h, uApplID) < 0)
   {
      pSc->uRc = CME_OS_RESOURCE_ERROR;
   }
   
   /* wait for the response of the controller */
   (void) cv_timedwait (&(pSc->cvNotify), &(pSc->mtxAccess), 30 * hz);
   pSc->fWaitingForRc = 0;
   uRes = pSc->uRc;
   
   if (uRes == CAPI_OK)
   {
      DBG (LOG_DEBUG, pSc->iUnit, "releasing appl. id %u successful",
           uApplID);
   }
   else
   {
      DBG (LOG_ERROR, pSc->iUnit, "error 0x%04x releasing appl. %u",
	   uRes, uApplID);
   }

   return (uRes);
} /* avmio_app_release */





/*
        receive information about a new established B3 connection
        ---------------------------------------------------------
        Note: I/o based controllers do only own one port.
*/

void avmio_receive_new_ncci
   (AvmAicSc_t *pSc)
{
   bus_space_tag_t    t = pSc->resInfo.ioTag;
   bus_space_handle_t h = pSc->resInfo.ioHandle;
   unsigned           uApplID;
   u_int32_t          dwNcci;
   unsigned           uWinSize;
   
   DBG (LOG_DEBUG, pSc->iUnit, "Got AVMAIC_RECEIVE_NEW_NCCI");

   /* read the data for the controller message */
   uApplID  = avmio_get_word (t, h);
   dwNcci   = avmio_get_word (t, h);
   uWinSize = avmio_get_word (t, h);
   
   /* check for valid window size */
   if (uWinSize > 8)
   {
      printf ("%s%d: %s: Got invalid window size %u, use %u\n",
              pSc->szDriverName, pSc->iUnit, __FUNCTION__, uWinSize, 8);
      uWinSize = 8;
   }
   
   DBG (LOG_TRACE, pSc->iUnit,
        "New B3 connection signaled, appl. id %u, NCCI 0x%08X, window size %u",
        uApplID, (unsigned) dwNcci, uWinSize);
    
} /* avmio_receive_new_ncci */
   




/*
        receive information about a released B3 connection or application
        -----------------------------------------------------------------
        Note: I/o based controllers do only own one port.
*/

void avmio_receive_free_ncci
   (AvmAicSc_t *pSc)
{
   bus_space_tag_t    t = pSc->resInfo.ioTag;
   bus_space_handle_t h = pSc->resInfo.ioHandle;
   unsigned           uApplID;
   u_int32_t          dwNcci;
   
   DBG (LOG_DEBUG, pSc->iUnit, "Got AVMAIC_RECEIVE_FREE_NCCI");

   /* read the data for the controller message */
   uApplID = avmio_get_word (t, h);
   dwNcci  = avmio_get_word (t, h);
   
   /* distinguish between application release or NCCI release */
   if (dwNcci == 0xFFFFFFFF)
   {
      /* application release --> store the result of the release call and
       * wakeup any waiting process
       */
      DBG (LOG_DEBUG, pSc->iUnit, "Got release for appl. id %u",
           uApplID);
      pSc->uRc = CAPI_OK;
      if (pSc->fWaitingForRc)
      {
         pSc->fWaitingForRc = 0;
         cv_broadcast (&(pSc->cvNotify));
      }
      
      DBG (LOG_INFO, pSc->iUnit, "Appl. id %u released", uApplID);
   }
   else
   {
      /* NCCI release, nothing to do */
      DBG (LOG_TRACE, pSc->iUnit,
           "NCCI 0x%08X released for appl. id %u",
           (unsigned) dwNcci, uApplID);
   }
   
} /* avmio_receive_free_ncci */





/* === definition of private functions =================================== */





