/**
 * @file ix1a_bpci.c
 *
 * IX1a-BasicPCI - Routines and specifications for IX1 ISA boards.
 *
 * Copyright: 2004 Thomas Wintergerst. All rights reserved.
 *
 * $Id: ix1a_bpci.c,v 1.1.2.1 2006/02/28 21:35:21 thomas Exp $
 * $Project:    CAPI for BSD $
 * $Target:     ix1a - CAPI manager driver for IX1 active ISDN controllers $
 * @date        14.12.2004
 * @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/errno.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/endian.h>
#include <sys/mbuf.h>
#include <machine/bus.h>
#include <sys/rman.h>
#include <sys/bus.h>
#include <machine/resource.h>

/* Import includes */

#define __IX1A_BPCI__

/* Local includes */
#include <c4b/ix1a/ix1a_var.h>
#include <c4b/ix1a/ix1a_misc.h>
#include <c4b/ix1a/ix1a_bpci.h>
#include <c4b/ix1a/ix1a_shm.h>





/* === Private definitions =============================================== */





/* === Definitions for public declarations =============================== */





/* === Prototypes for private functions ================================== */





/**
 * Interrupt routine for Basic PCI boards.
 *
 * @param pSc                   I/O: The softc structure for the board raising
 *                                 the interrupt.
 *
 * @return Nothing.
 */
static void ix1abpci_interrupt_handler
   (Ix1aSc_t *pSc);

/**
 * Check if an interrupt is pending for a board.
 *
 * This function is called to check if the board specified has raised an
 * interrupt and is waiting for an acknowledge.
 *
 * @param pSc                   I/O: The board to check for a pending
 *                                 interrupt.
 *
 * @retval 0                    No interrupt is pending from the board.
 * @retval 1                    The board raised an interrupt and is waiting
 *                              for an acknowledge.
 */
static int ix1abpci_is_int_pending
   (Ix1aSc_t *pSc);

/**
 * Acknowledge a pending interrupt to a board.
 *
 * This function is called to acknowledge a pending interrupt to the board
 * specified. The acknowledge operation is to toggle the correct bit in the
 * status register 2 shortly from 0 to 1 and back to 0.
 *
 * @param pSc                   I/O: The board to acknowledge the interrupt to.
 *
 * @return Nothing.
 */
static void ix1abpci_int_ack
   (Ix1aSc_t *pSc);





/* === Implementation of public functions ================================ */





/**
 *  Initialise and fill the resource info structure for a Basic PCI board.
 *
 * This function will initialise the resource info structure for Basic PCI
 * boards and acquire the needed resources.
 *
 * @param dev                   I: The device entry for the board to register.
 * @param pResInfo              I/O: The resource info structure to fill.
 *
 * @retval 0                    All resources could be acquired.
 * @retval Else                 At least one resource could not be acquired.
 *                              In fact no resource is required in this case.
 */

int ix1abpci_init_resinfo
   (device_t            dev,
    Ix1aResourceInfo_t *pResInfo)
{
   unsigned long ulBase;
   unsigned long ulCount;
   int           iRes;

   pResInfo->iRidIoHeader = 0x10;
   iRes = bus_get_resource (dev, SYS_RES_IOPORT, pResInfo->iRidIoHeader,
                            &ulBase, &ulCount);
   if (iRes != 0)
   {
      device_printf (dev,
                     "Unable to get PCI header i/o port range (error code %d)\n",
                     iRes);
      return (ENXIO);
   }
   pResInfo->iIoBaseHeader = (int) ulBase;
   
   pResInfo->iRidIoData = 0x14;
   iRes = bus_get_resource (dev, SYS_RES_IOPORT, pResInfo->iRidIoData,
                            &ulBase, &ulCount);
   if (iRes != 0)
   {
      device_printf (dev,
                     "Unable to get data i/o port range (error code %d)\n",
                      iRes);
      return (ENXIO);
   }
   pResInfo->iIoBaseData = (int) ulBase;

   pResInfo->iRidIoCmd = 0x18;
   iRes = bus_get_resource (dev, SYS_RES_IOPORT, pResInfo->iRidIoCmd,
                            &ulBase, &ulCount);
   if (iRes != 0)
   {
      device_printf (dev,
                     "unable to get cmd i/o port range (error code %d)\n",
                     iRes);
      return (ENXIO);
   }
   pResInfo->iIoBaseCmd = (int) ulBase;

   pResInfo->iRidIrq = 0;
   iRes = bus_get_resource (dev, SYS_RES_IRQ, pResInfo->iRidIrq,
                            &ulBase, &ulCount);
   if (iRes != 0)
   {
      device_printf (dev, "Unable to get irq number (error code %d)\n",
                     iRes);
      return (ENXIO);
   }
   pResInfo->iIrq = (int) ulBase;
   
   pResInfo->pResIoHeader = bus_alloc_resource
                               (dev, SYS_RES_IOPORT, &(pResInfo->iRidIoHeader),
                                0UL, ~0UL, 1, RF_ACTIVE);
   if (pResInfo->pResIoHeader == NULL)
   {
      device_printf (dev,
                     "Unable to allocate PCI header i/o port range at 0x%03X\n",
                     pResInfo->iIoBaseHeader);
      (void) ix1amisc_release_resinfo (dev, pResInfo);
      return (ENXIO);
   }
   pResInfo->ioTagHeader    = rman_get_bustag (pResInfo->pResIoHeader);
   pResInfo->ioHandleHeader = rman_get_bushandle (pResInfo->pResIoHeader);
   
   pResInfo->pResIoData = bus_alloc_resource
                             (dev, SYS_RES_IOPORT, &(pResInfo->iRidIoData),
                              0UL, ~0UL, 1, RF_ACTIVE);
   if (pResInfo->pResIoData == NULL)
   {
      device_printf (dev,
                     "Unable to allocate data i/o port range at 0x%03X\n",
                     pResInfo->iIoBaseData);
      (void) ix1amisc_release_resinfo (dev, pResInfo);
      return (ENXIO);
   }
   pResInfo->ioTagData    = rman_get_bustag (pResInfo->pResIoData);
   pResInfo->ioHandleData = rman_get_bushandle (pResInfo->pResIoData);
   
   pResInfo->pResIoCmd = bus_alloc_resource
                            (dev, SYS_RES_IOPORT, &(pResInfo->iRidIoCmd),
                             0UL, ~0UL, 1, RF_ACTIVE);
   if (pResInfo->pResIoCmd == NULL)
   {
      device_printf (dev,
                     "Unable to allocate cmd i/o port range at 0x%03X\n",
                     pResInfo->iIoBaseCmd);
      (void) ix1amisc_release_resinfo (dev, pResInfo);
      return (ENXIO);
   }
   pResInfo->ioTagCmd    = rman_get_bustag (pResInfo->pResIoCmd);
   pResInfo->ioHandleCmd = rman_get_bushandle (pResInfo->pResIoCmd);
   
   pResInfo->pResIrq = bus_alloc_resource
                          (dev, SYS_RES_IRQ, &(pResInfo->iRidIrq),
                           0UL, ~0UL, 1, RF_ACTIVE | RF_SHAREABLE);
   if (pResInfo->pResIrq == NULL)
   {
      device_printf (dev, "Unable to allocate irq %d\n",
                     pResInfo->iIrq);
      (void) ix1amisc_release_resinfo (dev, pResInfo);
      return (ENXIO);
   }
   /* Note: The interrupt routine will be setup by the caller when apropriate
    */
                            
   return (0);       
} /* ix1abpci_init_resinfo */





/**
 * Initialise Basic PCI board for operation.
 *
 * @param dev                   I: The device representing the board.
 * @param pSc                   I/O: The softc structure for the board.
 *
 * @retval 0                    The tests were successful, board exists and is
 *                              working.
 * @retval Else                 Error occurred, board does not exist or is not
 *                              working.
 */

int ix1abpci_init_board
   (device_t  dev,
    Ix1aSc_t *pSc)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagHeader;
   bus_space_handle_t h = pSc->resInfo.ioHandleHeader;
   unsigned           u;
   int                iRes;
   
   DBG (LOG_DEBUG, pSc->iUnit, "Start board initialisation");
   
   /* do the real initialisation, not documented to fail */
   bus_space_write_1 (t, h, IX1A_REG_AMCC_INTCSR + 1, 0x10);
   u = bus_space_read_2 (t, h, IX1A_REG_AMCC_IMB1);
   DBG (LOG_DEBUG, pSc->iUnit, "IMB1 register delivers 0x%04X", u);
   
   /* like the ISA boards set RESET bit, unset BOOT; keep in mind that the
    * Basic PCI does not support 8bit mode and has no ENABLE bit
    */
   bus_space_write_1 (t, h, IX1A_REG_PCICMD_CMD1, IX1A_CMD1_RESET);
   
   /* now check the shared memory */
   DBG (LOG_DEBUG, pSc->iUnit, "Start checking shared memory");
   ix1abpci_shm_write_word (pSc, 0x0, IX1A_SHM_TEST_WORD_LOW);
   ix1abpci_shm_write_word (pSc, IX1A_SHM_SIZE_BPCI - 2,
                            IX1A_SHM_TEST_WORD_HIGH);
   iRes = ix1abpci_shm_read_word (pSc, 0x0, &u);
   if (iRes != 0)
   {
      device_printf (dev,
                     "Shared memory error, unable to read from address 0x%04X\n",
                     0);
      ix1abpci_disable_board (pSc);
      return (iRes);
   }
   if (u != IX1A_SHM_TEST_WORD_LOW)
   {
      device_printf (dev,
                     "Shared memory error, wrote 0x%04X to address 0x%04X, but read 0x%04X\n",
                     IX1A_SHM_TEST_WORD_LOW, 0x0, u);
      ix1abpci_disable_board (pSc);
      return (EIO);
   }
   iRes = ix1abpci_shm_read_word (pSc, IX1A_SHM_SIZE_BPCI - 2, &u);
   if (iRes != 0)
   {
      device_printf (dev,
                     "Shared memory error, unable to read from address 0x%04X\n",
                     IX1A_SHM_SIZE_BPCI - 2);
      ix1abpci_disable_board (pSc);
      return (iRes);
   }
   if (u != IX1A_SHM_TEST_WORD_HIGH)
   {
      device_printf (dev,
                     "Shared memory error, wrote 0x%04X to address 0x%04X, but read 0x%04X\n",
                     IX1A_SHM_TEST_WORD_HIGH, IX1A_SHM_SIZE_BPCI - 2, u);
      ix1abpci_disable_board (pSc);
      return (EIO);
   }
   DBG (LOG_DEBUG, pSc->iUnit, "Shared memory test successful");
   
   /* finally enable the interrupt routine to handle interrupts from the board
    */
   if (! pSc->resInfo.fIrqSetup)
   {
      iRes = bus_setup_intr (dev, pSc->resInfo.pResIrq,
                             INTR_TYPE_NET | INTR_MPSAFE,
                             (driver_intr_t *) ix1abpci_interrupt_handler, pSc,
                             &(pSc->resInfo.hIrqCookie));
      if (iRes != 0)
      {
         device_printf (dev, "Error setting up interrupt handler: %d\n",
                        iRes);
         ix1abpci_disable_board (pSc);
         return (iRes);
      }
      pSc->resInfo.fIrqSetup = 1;
   }

   DBG (LOG_TRACE, pSc->iUnit, "Board initialised successfully");
   return (0);
} /* ix1abpci_init_board */





/**
 * Enable a Basic PCI board after disabling it.
 *
 * @note This routine expects the board to have been correctly initialised
 *       before disabling.
 *
 * @param pSc                   I/O: The softc structure for the board to
 *                                 enable.
 *
 * @retval 0                    The board was successfully enabled.
 * @retval Else                 Enabling the board failed, the result is an
 *                              errno value.
 */

int ix1abpci_enable_board
   (Ix1aSc_t *pSc)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagHeader;
   bus_space_handle_t h = pSc->resInfo.ioHandleHeader;
   unsigned           u;
   int                iRes;
   
   DBG (LOG_DEBUG, pSc->iUnit, "Start enabling board");
   
   /* do the real initialisation, not documented to fail */
   bus_space_write_1 (t, h, IX1A_REG_AMCC_INTCSR + 1, 0x10);
   u = bus_space_read_2 (t, h, IX1A_REG_AMCC_IMB1);
   DBG (LOG_DEBUG, pSc->iUnit, "IMB1 register delivers 0x%04X", u);
   
   /* like the ISA boards set RESET bit, unset BOOT; keep in mind that the
    * Basic PCI does not support 8bit mode and has no ENABLE bit
    */
   bus_space_write_1 (t, h, IX1A_REG_PCICMD_CMD1, IX1A_CMD1_RESET);
   
   /* now check the shared memory */
   DBG (LOG_DEBUG, pSc->iUnit, "Start checking shared memory");
   ix1abpci_shm_write_word (pSc, 0x0, IX1A_SHM_TEST_WORD_LOW);
   ix1abpci_shm_write_word (pSc, IX1A_SHM_SIZE_BPCI - 2,
                            IX1A_SHM_TEST_WORD_HIGH);
   iRes = ix1abpci_shm_read_word (pSc, 0x0, &u);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Shared memory error, unable to read from address 0x%04X\n",
           IX1A_SHM_SIZE_BPCI - 2);
      ix1abpci_disable_board (pSc);
      return (iRes);
   }
   if (u != IX1A_SHM_TEST_WORD_LOW)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Shared memory error, wrote 0x%04X to address 0x%04X, but read 0x%04X\n",
           IX1A_SHM_TEST_WORD_LOW, 0x0, u);
      ix1abpci_disable_board (pSc);
      return (EIO);
   }
   iRes = ix1abpci_shm_read_word (pSc, IX1A_SHM_SIZE_BPCI - 2, &u);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Shared memory error, unable to read from address 0x%04X\n",
           IX1A_SHM_SIZE_BPCI - 2);
      ix1abpci_disable_board (pSc);
      return (iRes);
   }
   if (u != IX1A_SHM_TEST_WORD_HIGH)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Shared memory error, wrote 0x%04X to address 0x%04X, but read 0x%04X\n",
           IX1A_SHM_TEST_WORD_HIGH, IX1A_SHM_SIZE_BPCI - 2, u);
      ix1abpci_disable_board (pSc);
      return (EIO);
   }
   
   DBG (LOG_TRACE, pSc->iUnit, "Board enabled successfully");
   return (0);
} /* ix1abpci_enable_board */





/**
 * Disable a Basic PCI board.
 *
 * @param pSc                   I/O: The softc structure for the board to
 *                                 disable.
 *
 * @return Nothing.
 */

void ix1abpci_disable_board
   (Ix1aSc_t *pSc)
{
   bus_space_tag_t    t;
   bus_space_handle_t h;

   /* if the resource structure is not initialised, the board should already
    * or still be disabled, no action needed here
    */
   if (pSc->resInfo.pResIoHeader == NULL)
   {
      DBG (LOG_DEBUG, pSc->iUnit,
           "Resources not initialised, board is expected to already be disabled");
      return;
   }
   t = pSc->resInfo.ioTagHeader;
   h = pSc->resInfo.ioHandleHeader;
   
   DBG (LOG_DEBUG, pSc->iUnit, "Start disabling board");
   
   /* Only a guess: Disabling the board means acknowledge all pending
    *               interrupts and set AMCC-INTCSR (low word) to 0.
    */
   
   bus_space_write_2 (t, h, IX1A_REG_AMCC_INTCSR + 2, 0x0002);
   (void) bus_space_read_2 (t, h, IX1A_REG_AMCC_IMB1);
   (void) bus_space_read_2 (t, h, IX1A_REG_AMCC_OMB1);
   bus_space_write_2 (t, h, IX1A_REG_AMCC_INTCSR, 0);
   
   DBG (LOG_DEBUG, pSc->iUnit, "Board disabled");
   
} /* ix1abpci_disable_board */





/**
 * Set the offset pointer within a Basic PCI board's shared memory.
 *
 * For Basic PCI boards this seems to be a non-reliant operation, according to
 * the original Linux and DOS driver sources from ITK / Digi Europe. Before and
 * after writing to the offset register one or two i/o read operations seem to
 * be necessary and the write operation to the offset register may even fail
 * for some attempts. So we implement this operation with a loop that checks
 * the result of the operation and performs a number of attempts to set the
 * offset register.
 *
 * @param pSc                   I/O: The softc structure for the board to set
 *                                 the offset register.
 * @param uAddr                 I: The address offset to set.
 *
 * @retval 0                    The offset register was set successfully.
 * @retval Else                 The offset register could not be set, board
 *                              failure.
 */

int ix1abpci_shm_set_offset
   (Ix1aSc_t *pSc,
    unsigned  uAddr)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagCmd;
   bus_space_handle_t h = pSc->resInfo.ioHandleCmd;
   unsigned           uRealAddr = uAddr >> 1;
   unsigned           uTmp;
   int                i;
   
   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid address 0x%04X to set shared memory offset to (must be even)",
           uAddr);
      return (EINVAL);
   }
   
   for (i = 0; i < 100; ++i)
   {
      /*(void) bus_space_read_1 (t, 0x3FF, 0);*/
      (void) bus_space_read_1 (t, 0x3FF, 0);
      bus_space_write_2 (t, h, IX1A_REG_PCICMD_OFFSET, htole16 (uRealAddr));
      /*(void) bus_space_read_1 (t, 0x3FF, 0);*/
      (void) bus_space_read_1 (t, 0x3FF, 0);
      
      uTmp = le16toh (bus_space_read_2 (t, h, IX1A_REG_PCICMD_OFFSET));
      if (uTmp == uRealAddr)
      {
         return (0);
      }
      DBG (LOG_DEBUG, pSc->iUnit,
           "Setting shared memory offset pointer failed, attempt %d, 16bit offset 0x%04X, resulting offset 0x%04X",
           i + 1, uRealAddr, uTmp);
   }

   DBG (LOG_ERROR, pSc->iUnit,
        "Unable to set shared memory offset pointer to 0x%04X (16bit offset 0x%04X)",
        uAddr, uRealAddr);
   return (EIO);
} /* ix1abpci_shm_set_offset */





/**
 * Read a 16bit word from a shared memory address of a Basic PCI board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to read from.
 * @param puValue               O: The word read from the board, only valid if
 *                                 the result is 0.
 *
 * @retval 0                    The value was read successfully and stored into
 *                              *puValue.
 * @retval Else                 An errno value on failure.
 */

int ix1abpci_shm_read_word
   (Ix1aSc_t *pSc,
    unsigned  uAddr,
    unsigned *puValue)
{
   int iRes;

   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0 || puValue == NULL)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid shared memory address 0x%04X to read a word from",
           uAddr);
      return (EINVAL);
   }
   
   /* set the shared memory pointer to the requested address */
   iRes = ix1abpci_shm_set_offset (pSc, uAddr);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Failed to read word from shared memory, unable to set offset address 0x%04X",
           uAddr);
      return (iRes);
   }

   /* now read the value at the current offset */
   *puValue = le16toh (bus_space_read_2 (pSc->resInfo.ioTagData,
                                         pSc->resInfo.ioHandleData,
                                         0));
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Read word 0x%04X from shared memory address 0x%04X",
        *puValue, uAddr);
   return (0);
} /* ix1abpci_shm_read_word */





/**
 * Read a block of data from a shared memory address of a Basic PCI board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to read from.
 * @param pbData                I: The address for returing the data read.
 * @param nLenData              I: The length in bytes of the data block to
 *                                 read.
 *
 * @retval 0                    The data block was read successfully.
 * @retval Else                 Failure, result is an errno value.
 */

int ix1abpci_shm_read_block_16
   (Ix1aSc_t *pSc,
    unsigned  uAddr,
    u_int8_t *pbData,
    size_t    nLenData)
{
   u_int16_t w;
   int       iRes;
   
   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid shared memory address 0x%04X to read block of length %zu from",
           uAddr, nLenData);
      return (EINVAL);
   }
   
   /* set the shared memory pointer to the requested address */
   iRes = ix1abpci_shm_set_offset (pSc, uAddr);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Failed to read block of size %zu from shared memory, unable to set offset address 0x%04X",
           nLenData, uAddr);
      return (iRes);
   }
   
   /* now read the block from the current offset; maybe the last byte is
    * missing if the length is odd
    */
   bus_space_read_multi_2 (pSc->resInfo.ioTagData, pSc->resInfo.ioHandleData,
                           0, (u_int16_t *) pbData, nLenData >> 1);
   
   /* If the length is odd, there is still a byte to read. As we are in 16bit
    * mode we must read a word and extract the low byte from it (in little
    * endian byte order).
    */
   if ((nLenData & 0x01) != 0)
   {
      w = le16toh (bus_space_read_2
                      (pSc->resInfo.ioTagData, pSc->resInfo.ioHandleData, 0));
      pbData [nLenData - 1] = (w & 0xFF);
   }
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Read %zu bytes from shared memory starting at address 0x%04X",
        nLenData, uAddr);
   return (0);
} /* ix1abpci_shm_read_block_16 */





/**
 * Write a 16bit word to a shared memory address of a Basic PCI board.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to write to.
 * @param uValue                I: The word to write.
 *
 * @retval 0                    The value was successfully written.
 * @retval Else                 An errno value on failure.
 */

int ix1abpci_shm_write_word
   (Ix1aSc_t *pSc,
    unsigned  uAddr,
    unsigned  uValue)
{
   int iRes;

   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid shared memory address 0x%04X to write word 0x%04X to",
           uAddr, uValue);
      return (EINVAL);
   }
   
   /* set the shared memory pointer to the requested address */
   iRes = ix1abpci_shm_set_offset (pSc, uAddr);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Failed to write word 0x%04X to shared memory, unable to set offset address 0x%04X",
           uValue, uAddr);
      return (iRes);
   }

   /* now write the value to the current offset */
   bus_space_write_2 (pSc->resInfo.ioTagData, pSc->resInfo.ioHandleData, 0,
                      htole16 (uValue));
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Wrote word 0x%04X to shared memory address 0x%04X",
        uValue, uAddr);
   return (0);
} /* ix1abpci_shm_write_word */





/**
 * Write a block of data to a shared memory address of a Basic PCI board using
 * 16bit mode to write every single byte.
 *
 * This function is called for boot code download to write the boot code. Every
 * single byte must be written using 16bit i/o, else the shared memory pointer
 * would not increment for the Basic PCI.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to write to.
 * @param pbData                I: The address of the data block to write.
 * @param nLenData              I: The length in bytes of the data block to
 *                                 write.
 *
 * @retval 0                    The data block was written successfully.
 * @retval Else                 Failure, result is an errno value.
 */

int ix1abpci_shm_write_block_8
   (Ix1aSc_t       *pSc,
    unsigned        uAddr,
    const u_int8_t *pbData,
    size_t          nLenData)
{
   const u_int8_t *p;
   size_t          n;
   u_int16_t       w;
   int             iRes;
   
   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid shared memory address 0x%04X to write block of length %zu to",
           uAddr, nLenData);
      return (EINVAL);
   }
   
   /* set the shared memory pointer to the requested address */
   iRes = ix1abpci_shm_set_offset (pSc, uAddr);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Failed to write block of length %zu to shared memory, unable to set offset address 0x%04X",
           nLenData, uAddr);
      return (iRes);
   }
   
   /* now write the block to the current offset, one byte after each other */
   for (p = pbData, n = 0; n < nLenData; ++p, ++n)
   {
      w = htole16 ((u_int16_t) (*p));
      bus_space_write_2 (pSc->resInfo.ioTagData, pSc->resInfo.ioHandleData,
                         0, w);
   }
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Wrote %zu bytes as words to shared memory starting at address 0x%04X",
        nLenData, uAddr);
   return (0);
} /* ix1abpci_shm_write_block_8 */





/**
 * Write a block of data to a shared memory address of a Basic PCI board using
 * 16bit mode.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to write to.
 * @param pbData                I: The address of the data block to write.
 * @param nLenData              I: The length in bytes of the data block to
 *                                 write.
 *
 * @retval 0                    The data block was written successfully.
 * @retval Else                 Failure, result is an errno value.
 */

int ix1abpci_shm_write_block_16
   (Ix1aSc_t       *pSc,
    unsigned        uAddr,
    const u_int8_t *pbData,
    size_t          nLenData)
{
   int iRes;
   
   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid shared memory address 0x%04X to write block of length %zu to",
           uAddr, nLenData);
      return (EINVAL);
   }
   
   /* set the shared memory pointer to the requested address */
   iRes = ix1abpci_shm_set_offset (pSc, uAddr);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Failed to write block of length %zu to shared memory, unable to set offset address 0x%04X",
           nLenData, uAddr);
      return (iRes);
   }
   
   /* now write the block to the current offset; maybe the last byte is missing
    * if the length is odd
    */
   bus_space_write_multi_2 (pSc->resInfo.ioTagData,
                            pSc->resInfo.ioHandleData,
                            0, (const u_int16_t *) pbData, nLenData >> 1);
   
   /* If the length is odd, there is still a byte to write. As we are in 16bit
    * mode we must construct a word to write it.
    */
   if ((nLenData & 0x01) != 0)
   {
      u_int16_t w;

      w = htole16 ((u_int16_t) (pbData [nLenData - 1]));
      bus_space_write_2 (pSc->resInfo.ioTagData,
                         pSc->resInfo.ioHandleData,
                         0, w);
   }
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Wrote %zu bytes to shared memory starting at address 0x%04X",
        nLenData, uAddr);
   return (0);
} /* ix1abpci_shm_write_block_16 */





/**
 * Compare a block of data to a shared memory address of a Basic PCI board
 * using 16bit mode.
 *
 * @param pSc                   I/O: The softc structure for the board.
 * @param uAddr                 I: The shared memory address to write to.
 * @param pbData                I: The address of the data block to write.
 * @param nLenData              I: The length in bytes of the data block to
 *                                 write.
 * @param pfResult              O: The compare result (0 - failure, !0 -
 *                                 success).
 *
 * @retval 0                    The data block was compared successfully,
 *                              compare result is stored into *pfResult.
 * @retval Else                 Failure, result is an errno value.
 */

int ix1abpci_shm_compare_block_16
   (Ix1aSc_t       *pSc,
    unsigned        uAddr,
    const u_int8_t *pbData,
    size_t          nLenData,
    int            *pfResult)
{
   const u_int16_t *p = (const u_int16_t *) pbData;
   size_t           nNumWords = nLenData >> 2;
   size_t           nCurrPos;
   u_int16_t        w;
   int              iRes;
   
   /* check if an even address was specified */
   if ((uAddr & 0x01) != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Invalid shared memory address 0x%04X to compare block of length %zu to",
           uAddr, nLenData);
      return (EINVAL);
   }
   
   /* set the shared memory pointer to the requested address */
   iRes = ix1abpci_shm_set_offset (pSc, uAddr);
   if (iRes != 0)
   {
      DBG (LOG_ERROR, pSc->iUnit,
           "Failed to compare block of length %zu to shared memory, unable to set offset address 0x%04X",
           nLenData, uAddr);
      return (iRes);
   }
   
   /* now read the necessare words and compare them to the data specified */
   for (nCurrPos = 0; nCurrPos < nNumWords; ++nCurrPos)
   {
      w = le16toh (bus_space_read_2 (pSc->resInfo.ioTagData,
                                     pSc->resInfo.ioHandleData,
                                     0));
      if (w != p [nCurrPos])
      {
         DBG (LOG_INFO, pSc->iUnit,
              "Compare failed at position 0x%04X (read 0x%04X, expected 0x%04X)",
              (unsigned) (nCurrPos * 2),
              (unsigned) w, (unsigned) p [nCurrPos]);
         *pfResult = 0;
         return (0);
      }
   }
   
   /* If the length is odd, there is still a byte to compare. As we are in
    * 16bit mode we must read it as a word.
    */
   if ((nLenData & 0x01) != 0)
   {
      w = le16toh (bus_space_read_2 (pSc->resInfo.ioTagData,
                                     pSc->resInfo.ioHandleData,
                                     0)) & 0xFF;
      if (w != (u_int16_t) pbData [nLenData - 1])
      {
         DBG (LOG_INFO, pSc->iUnit,
              "Compare failed at position 0x%04X (read 0x%04X, expected 0x%04X)",
              (unsigned) (nLenData - 1),
              (unsigned) w, (unsigned) pbData [nLenData - 1]);
         *pfResult = 0;
         return (0);
      }
   }
   
   DBG (LOG_DEBUG, pSc->iUnit,
        "Compared %zu bytes to shared memory starting at address 0x%04X",
        nLenData, uAddr);
   *pfResult = 1;
   return (0);
} /* ix1abpci_shm_compare_block_16 */





/* === Implementation of private functions =============================== */





/**
 * Interrupt routine for Basic PCI boards.
 *
 * @param pSc                   I/O: The softc structure for the board raising
 *                                 the interrupt.
 *
 * @return Nothing.
 */

static void ix1abpci_interrupt_handler
   (Ix1aSc_t *pSc)
{
   DBG (LOG_IRQ, pSc->iUnit, "Interrupt handler called");
        
   mtx_lock (&(pSc->mtxAccess));
   if (pSc->fOpInProgress != 0 && ! pSc->fWaitingForRc)
   {
      /* Note: This may happen because for each interrupt we loop to get
       *       several messages if available. But the board raises an interrupt
       *       for every message delivered. So after fetching more than one
       *       message the next interrupt may occur before a result waiting
       *       function call took its result and so this operation is still in
       *       progress, although its return code is already stored to be
       *       fetched.
       */
      DBG (LOG_DEBUG, pSc->iUnit, "Operation still in progress");
   }
   if (pSc->fIntrActive != 0)
   {
      mtx_unlock (&(pSc->mtxAccess));
      DBG (LOG_ERROR, pSc->iUnit, "Nested interrupt handler call");
      return;
   }
   pSc->fIntrActive = 1;
   
   /* check if this board really delivered the interrupt */
   if (! ix1abpci_is_int_pending (pSc))
   {
      pSc->fIntrActive = 0;
      mtx_unlock (&(pSc->mtxAccess));
      DBG (LOG_IRQ, pSc->iUnit, "No interrupt pending");
      return;
   }
   
   /* acknowledge the pending interrupt to the board */
   ix1abpci_int_ack (pSc);
   
   /* if there is someone waiting for the board to deliver an interrupt, tell
    * him it has happened
    */
   if (pSc->fOpInProgress && pSc->fWaitingForRc)
   {
      pSc->uRc = CAPI_OK;
      pSc->fWaitingForRc = 0;
      cv_broadcast (&(pSc->cvNotify));

      DBG (LOG_DEBUG, pSc->iUnit, "Woken up waiting thread");
      
      /* we proceed nevertheless with handling CAPI messages from the board */
   }
   
   /* if the board is in READY state or above, we now must read all available
    * CAPI message out of the shared memory
    */
   if (pSc->state >= IX1A_STATE_READY)
   {
      bus_space_tag_t    t;
      bus_space_handle_t h;
      u_int16_t          w;
      
      /* update the watchdog word */
      t = pSc->resInfo.ioTagData;
      h = pSc->resInfo.ioHandleData;
      (void) ix1abpci_shm_set_offset (pSc, IX1A_SHM_OFS_CAPI_BEAT_BOARD);
      w = bus_space_read_2 (t, h, IX1A_REG_DATA_LOW);
      bus_space_write_2 (t, h, IX1A_REG_DATA_LOW, w);
      DBG (LOG_IRQ, pSc->iUnit,
           "Watchdog word updated, value 0x%04X",
           (unsigned) w);

      ix1ashm_read_capi_messages (pSc);
   }

   pSc->fIntrActive = 0;
   mtx_unlock (&(pSc->mtxAccess));
   
   DBG (LOG_IRQ, pSc->iUnit, "Left interrupt handler");
        
} /* ix1abpci_interrupt_handler */





/**
 * Check if an interrupt is pending for a board.
 *
 * This function is called to check if the board specified has raised an
 * interrupt and is waiting for an acknowledge.
 *
 * @param pSc                   I/O: The board to check for a pending
 *                                 interrupt.
 *
 * @retval 0                    No interrupt is pending from the board.
 * @retval 1                    The board raised an interrupt and is waiting
 *                              for an acknowledge.
 */

static int ix1abpci_is_int_pending
   (Ix1aSc_t *pSc)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagHeader;
   bus_space_handle_t h = pSc->resInfo.ioHandleHeader;
   unsigned           u;
   
   /* check status register 2 for the INT-PENDING bit */
   u = le16toh (bus_space_read_2 (t, h, IX1A_REG_AMCC_INTCSR + 2));
   if ((u & 0x0002) != 0)
   {
      u = le16toh (bus_space_read_2 (t, h, IX1A_REG_AMCC_MBEF + 2));
      if ((u & 0x0001) != 0)
      {
         return (1);
      }
   }
   return (0);
} /* ix1abpci_is_int_pending */





/**
 * Acknowledge a pending interrupt to a board.
 *
 * This function is called to acknowledge a pending interrupt to the board
 * specified. The acknowledge operation is to toggle the correct bit in the
 * status register 2 shortly from 0 to 1 and back to 0.
 *
 * @param pSc                   I/O: The board to acknowledge the interrupt to.
 *
 * @return Nothing.
 */

static void ix1abpci_int_ack
   (Ix1aSc_t *pSc)
{
   bus_space_tag_t    t = pSc->resInfo.ioTagHeader;
   bus_space_handle_t h = pSc->resInfo.ioHandleHeader;
   unsigned           u;
   
   u = le16toh (bus_space_read_2 (t, h, IX1A_REG_AMCC_INTCSR + 2));
   u |= 0x0002;
   bus_space_write_2 (t, h, IX1A_REG_AMCC_INTCSR + 2, htole16 (u));
   (void) bus_space_read_2 (t, h, IX1A_REG_AMCC_IMB1);
   
} /* ix1abpci_int_ack */
