/******************************************************************************
 Hardware Programming Interface (HPI) for AudioScience ASI6200 series adapters.
 These PCI bus adapters are based on the TI C6711 DSP.

 Entry point:
   int HPI6700_adapter_init(struct hpi_adapter_obj *pao);

 #defines
   GENERATE_PCI_ASSERTS to generate asserts from PCI2040 errors

    Copyright (C) 1997-2017  AudioScience Inc. <support@audioscience.com>

    This program is free software; you can redistribute it and/or modify
    it under the terms of version 2 of the GNU General Public License as
    published by the Free Software Foundation;

    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.
*******************************************************************************/
#define SOURCEFILE_NAME "hpi6000.c"

#include "hpi_internal.h"
#include "hpimsginit.h"
#include "hpimsgx.h"
#include "hpidebug.h"
#include "hpi6000.h"
#include "hpidspcd.h"
#include "hpicmn.h"

#define HPI_HIF_BASE (0x00000200)	/* start of C67xx internal RAM */
#define HPI_HIF_ADDR(member) \
	(HPI_HIF_BASE + offsetof(struct hpi_hif_6000, member))
#define HPI_HIF_ERROR_MASK	0x4000

// local defines

// for PCI2040 i/f chip
// HPI CSR registers
// word offsets from CSR base
// use when io addresses defined as uint32_t *

#define INTERRUPT_EVENT_SET	0
#define INTERRUPT_EVENT_CLEAR	1
#define INTERRUPT_MASK_SET	2
#define INTERRUPT_MASK_CLEAR	3
#define HPI_ERROR_REPORT	4
#define HPI_RESET		5
#define HPI_DATA_WIDTH		6

#define MAX_DSPS 2
// HPI registers, spaced 8K bytes = 2K words apart
#define DSP_SPACING		0x800

#define CONTROL			0x0000
#define ADDRESS			0x0200
#define DATA_AUTOINC		0x0400
#define DATA			0x0600

#define TIMEOUT 500000

struct dsp_obj {
	__iomem uint32_t *prHPIControl;
	__iomem uint32_t *prHPIAddress;
	__iomem uint32_t *prHPIData;
	__iomem uint32_t *prHPIDataAutoInc;
	char cDspRev;		//A, B
	uint32_t dwControlCacheAddressOnDsp;
	uint32_t dwControlCacheLengthOnDsp;
	struct hpi_adapter_obj *paParentAdapter;
};

struct hpi_hw_obj {
	__iomem uint32_t *dw2040_HPICSR;
	__iomem uint32_t *dw2040_HPIDSP;

	uint16_t wNumDsp;
	struct dsp_obj ado[MAX_DSPS];

	uint32_t dwMessageBufferAddressOnDsp;
	uint32_t dwResponseBufferAddressOnDsp;
	uint32_t dwPCI2040HPIErrorCount;

	struct hpi_control_cache_single aControlCache[HPI_NMIXER_CONTROLS];
	struct hpi_control_cache *pCache;
};

static uint16_t Hpi6000_DspBlockWrite32(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	uint32_t dwHpiAddress,
	uint32_t *dwSource,
	uint32_t dwCount
);
static uint16_t Hpi6000_DspBlockRead32(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	uint32_t dwHpiAddress,
	uint32_t *dwDest,
	uint32_t dwCount
);

static short Hpi6000_AdapterBootLoadDsp(
	struct hpi_adapter_obj *pao
);

static short Hpi6000_Check_PCI2040_ErrorFlag(
	struct hpi_adapter_obj *pao,
	uint16_t nReadOrWrite
);
#define H6READ 1
#define H6WRITE 0

static short Hpi6000_UpdateControlCache(
	struct hpi_adapter_obj *pao,
	struct hpi_message *phm
);
static short Hpi6000_MessageResponseSequence(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	struct hpi_message *phm,
	struct hpi_response *phr
);

static void HwMessage(
	struct hpi_adapter_obj *pao,
	struct hpi_message *phm,
	struct hpi_response *phr
);

static short Hpi6000_WaitDspAck(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	uint32_t dwAckValue
);

static short Hpi6000_SendHostCommand(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	uint32_t dwHostCmd
);

static void Hpi6000_SendDspInterrupt(
	struct dsp_obj *pdo
);

static short Hpi6000_SendData(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	struct hpi_message *phm,
	struct hpi_response *phr
);

static short Hpi6000_GetData(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	struct hpi_message *phm,
	struct hpi_response *phr
);

static void HpiWriteWord(
	struct dsp_obj *pdo,
	uint32_t dwAddress,
	uint32_t dwData
);

static uint32_t HpiReadWord(
	struct dsp_obj *pdo,
	uint32_t dwAddress
);

static void HpiWriteBlock(
	struct dsp_obj *pdo,
	uint32_t dwAddress,
	uint32_t *pdwData,
	uint32_t dwLength
);

static void HpiReadBlock(
	struct dsp_obj *pdo,
	uint32_t dwAddress,
	uint32_t *pdwData,
	uint32_t dwLength
);

static void AdapterDelete(
	struct hpi_adapter_obj *pao,
	struct hpi_message *phm,
	struct hpi_response *phr
);

static void AdapterGetAsserts(
	struct hpi_adapter_obj *pao,
	struct hpi_message *phm,
	struct hpi_response *phr
);

static void AdapterShutdown(struct hpi_adapter_obj *pao);

// local globals

static uint16_t gwPciReadAsserts;	// used to count PCI2040 errors
static uint16_t gwPciWriteAsserts;	// used to count PCI2040 errors

static void ControlMessage(
	struct hpi_adapter_obj *pao,
	struct hpi_message *phm,
	struct hpi_response *phr
)
{
	struct hpi_hw_obj *phw = pao->priv;

	switch (phm->wFunction) {
	case HPI_CONTROL_GET_STATE:
		if (pao->wHasControlCache) {
			phr->wError = Hpi6000_UpdateControlCache(pao, phm);

			if (phr->wError)
				break;

			if (HpiCheckControlCache(phw->pCache, phm, phr))
				break;
		}
		HwMessage(pao, phm, phr);
		break;
	case HPI_CONTROL_SET_STATE:
		HwMessage(pao, phm, phr);
		HpiCmn_ControlCache_SyncToMsg(phw->pCache, phm, phr);
		break;

	case HPI_CONTROL_GET_INFO:
	default:
		HwMessage(pao, phm, phr);
		break;
	}
}

static void AdapterMessage(
	struct hpi_adapter_obj *pao,
	struct hpi_message *phm,
	struct hpi_response *phr
)
{
	switch (phm->wFunction) {
	case HPI_ADAPTER_GET_ASSERT:
		AdapterGetAsserts(pao, phm, phr);
		break;

	case HPI_ADAPTER_DELETE:
		AdapterDelete(pao, phm, phr);
		break;

	default:
		HwMessage(pao, phm, phr);
		break;
	}
}

static void OStreamMessage(
	struct hpi_adapter_obj *pao,
	struct hpi_message *phm,
	struct hpi_response *phr
)
{
	switch (phm->wFunction) {
	case HPI_OSTREAM_HOSTBUFFER_ALLOC:
	case HPI_OSTREAM_HOSTBUFFER_FREE:
		/* Don't let these messages go to the HW function because
		 * they're called without locking the spinlock.
		 * For the HPI6000 adapters the HW would return
		 * HPI_ERROR_INVALID_FUNC anyway.
		 */
		phr->wError = HPI_ERROR_INVALID_FUNC;
		break;
	default:
		HwMessage(pao, phm, phr);
		return;
	}
}

static void IStreamMessage(
	struct hpi_adapter_obj *pao,
	struct hpi_message *phm,
	struct hpi_response *phr
)
{

	switch (phm->wFunction) {
	case HPI_ISTREAM_HOSTBUFFER_ALLOC:
	case HPI_ISTREAM_HOSTBUFFER_FREE:
		/* Don't let these messages go to the HW function because
		 * they're called without locking the spinlock.
		 * For the HPI6000 adapters the HW would return
		 * HPI_ERROR_INVALID_FUNC anyway.
		 */
		phr->wError = HPI_ERROR_INVALID_FUNC;
		break;
	default:
		HwMessage(pao, phm, phr);
		return;
	}
}

/*****************************************************************************/
/** Entry point to this HPI backend
 * All calls to the HPI start here
 */
static void HPI_6000(
		struct hpi_adapter_obj *pao,
		struct hpi_message *phm,
		struct hpi_response *phr
		)
{
	/* Don't even try to communicate with crashed DSP */
	if (pao->wDspCrashed >= 10) {
		HPI_InitResponse(phr, phm->wObject, phm->wFunction,
			HPI_ERROR_DSP_HARDWARE);
		HPI_DEBUG_LOG1(DEBUG, "Adapter %d dsp crashed\n",
			phm->wAdapterIndex);
		return;
	}
	/* Init default response including the size field */
	if (phm->wFunction != HPI_SUBSYS_CREATE_ADAPTER)
		HPI_InitResponse(phr, phm->wObject, phm->wFunction,
			HPI_ERROR_PROCESSING_MESSAGE);

	switch (phm->wType) {
	case HPI_TYPE_REQUEST:
		switch (phm->wObject) {
		case HPI_OBJ_SUBSYSTEM:
			phr->wError = HPI_ERROR_INVALID_FUNC;
			break;

		case HPI_OBJ_ADAPTER:
			phr->wSize =
				sizeof(struct hpi_response_header) +
				sizeof(struct hpi_adapter_res);
			AdapterMessage(pao, phm, phr);
			break;

		case HPI_OBJ_CONTROL:
			ControlMessage(pao, phm, phr);
			break;

		case HPI_OBJ_OSTREAM:
			OStreamMessage(pao, phm, phr);
			break;

		case HPI_OBJ_ISTREAM:
			IStreamMessage(pao, phm, phr);
			break;

		default:
			HwMessage(pao, phm, phr);
			break;
		}
		break;

	default:
		phr->wError = HPI_ERROR_INVALID_TYPE;
		break;
	}
}

/*****************************************************************************/
/* INITIALIZATION */

/* Map entire space of first two PCI BAR
 * The PCI2040 has the following address map
 * BAR0 - 4K = HPI control and status registers on PCI2040 (HPI CSR)
 * BAR1 - 32K = HPI registers on DSP
 */
#ifdef HPI_OS_LINUX_KERNEL
static hpi_err_t SetupBars(struct hpi_adapter_obj *pao)
{
	struct pci_dev *pci_dev = pao->os.pci_dev;
	struct hpi_hw_obj *phw = pao->priv;

	phw->dw2040_HPICSR = pcim_iomap(pci_dev, 0, 0);
	phw->dw2040_HPIDSP = pcim_iomap(pci_dev, 1, 0);
	if (!phw->dw2040_HPICSR || !phw->dw2040_HPIDSP) {
		dev_err(&pci_dev->dev, "pcim_iomap failed, aborting\n");
		return HPI_ERROR_MEMORY_ALLOC;
	}
	return 0;

}
#else
static hpi_err_t SetupBars(struct hpi_adapter_obj *pao)
{
	struct hpi_hw_obj *phw = pao->priv;

	phw->dw2040_HPICSR =
		(__iomem uint32_t *)pao->os.wdm_bus_info.Pci.MemoryInfo[0].Virtual;
	phw->dw2040_HPIDSP =
		(__iomem uint32_t *)pao->os.wdm_bus_info.Pci.MemoryInfo[1].Virtual;
	HPI_DEBUG_LOG2(VERBOSE, "csr %p, dsp %p\n",
		phw->dw2040_HPICSR, phw->dw2040_HPIDSP);
	return 0;
}
#endif

int HPI6000_adapter_init(struct hpi_adapter_obj *pao);

static const struct hpi_endpoint HPI6000_endpoint = {
	C99(.name =) "HPI6000",
	C99(.adapter_init =) HPI6000_adapter_init,
	C99(.shutdown =) AdapterShutdown,
	C99(.irq_query_and_clear =) NULL,
	C99(.transact =) HPI_6000
};

/** Initialize adapter
 * allocate buffers, bootload DSPs, initialise control cache
 * expects pao to have initialized:
 * linux: os.pci_dev
 * other: os.apMemBase, ?
 */
static int _HPI6000_adapter_init(struct hpi_adapter_obj *pao)
{
	uint16_t err = 0;
	uint32_t dwDspIndex = 0;
	uint32_t dwControlCacheSize = 0;
	uint32_t dwControlCacheCount = 0;
	struct hpi_hw_obj *phw;

	pao->ep = HPI6000_endpoint;

	phw = HpiOs_MemAllocZero(sizeof(struct hpi_hw_obj));
	if (!phw)
		return HPI_ERROR_MEMORY_ALLOC;
	pao->priv = phw;

	err = SetupBars(pao);
	if (err)
		return err;

	// set addresses for the possible DSP HPI interfaces
	for (dwDspIndex = 0; dwDspIndex < MAX_DSPS; dwDspIndex++) {
		phw->ado[dwDspIndex].prHPIControl =
			phw->dw2040_HPIDSP + (CONTROL +
			DSP_SPACING * dwDspIndex);

		phw->ado[dwDspIndex].prHPIAddress =
			phw->dw2040_HPIDSP + (ADDRESS +
			DSP_SPACING * dwDspIndex);
		phw->ado[dwDspIndex].prHPIData =
			phw->dw2040_HPIDSP + (DATA +
			DSP_SPACING * dwDspIndex);

		phw->ado[dwDspIndex].prHPIDataAutoInc =
			phw->dw2040_HPIDSP +
			(DATA_AUTOINC + DSP_SPACING * dwDspIndex);

		HPI_DEBUG_LOG4(VERBOSE, "ctl %p, adr %p, dat %p, dat++ %p\n",
			phw->ado[dwDspIndex].prHPIControl,
			phw->ado[dwDspIndex].prHPIAddress,
			phw->ado[dwDspIndex].prHPIData,
			phw->ado[dwDspIndex].prHPIDataAutoInc);

		phw->ado[dwDspIndex].paParentAdapter = pao;
	}

	phw->dwPCI2040HPIErrorCount = 0;
	pao->wHasControlCache = 0;

	HpiOs_SpinLock_Init(&pao->dspLock);

	// Set the default number of DSPs on this card
	// This is (conditionally) adjusted after bootloading
	// of the first DSP in the bootload section.
	phw->wNumDsp = 1;

	err = Hpi6000_AdapterBootLoadDsp(pao);
	if (err)
		return HPI_ERROR_DSP_BOOTLOAD;

	HPI_DEBUG_LOG0(INFO, "Bootload DSP OK\n");

	phw->dwMessageBufferAddressOnDsp = 0L;
	phw->dwResponseBufferAddressOnDsp = 0L;

	// get info about the adapter by asking the adapter
	// send a HPI_ADAPTER_GET_INFO message
	{
		struct hpi_message hm;
		struct hpi_response hr;	// response from DSP 0
		struct hpi_response hr1;	// response from DSP 1
		uint16_t wError = 0;

		HPI_InitMessageResponse(&hm, &hr, HPI_OBJ_ADAPTER, HPI_ADAPTER_GET_INFO);
		hm.wAdapterIndex = 0;
		memset(&hr1, 0, sizeof(hr1));
		hr1.wSize = sizeof(hr1);

		wError = Hpi6000_MessageResponseSequence(pao, 0, &hm, &hr);
		if (hr.wError) {
			HPI_DEBUG_LOG1(DEBUG, "message error %d\n",
				hr.wError);
			return hr.wError;
		}
		if (phw->wNumDsp == 2) {
			wError = Hpi6000_MessageResponseSequence(pao, 1, &hm,
				&hr1);
			if (wError)
				return wError;
		}
		pao->type = hr.u.ax.info.wAdapterType;
		pao->index = hr.u.ax.info.wAdapterIndex;
		pao->instreams = (uint8_t)hr.u.ax.info.wNumIStreams;
		pao->outstreams = (uint8_t)hr.u.ax.info.wNumOStreams;
	}

	memset(&phw->aControlCache[0], 0,
		sizeof(struct hpi_control_cache_single) *
		HPI_NMIXER_CONTROLS);
	// Read the control cache length to figure out if it is turned on
	dwControlCacheSize = HpiReadWord(&phw->ado[0],
		HPI_HIF_ADDR(dwControlCacheSizeInBytes));
	if (dwControlCacheSize)
	{
		dwControlCacheCount = HpiReadWord(&phw->ado[0],
			HPI_HIF_ADDR(dwControlCacheCount));

		phw->pCache = HpiAllocControlCache(
				dwControlCacheCount,
				dwControlCacheSize,
				(unsigned char *)
				&phw->aControlCache[0]
				);
		if (phw->pCache)
			pao->wHasControlCache = 1;
	}

	HPI_DEBUG_LOG2(DEBUG, "Get adapter info ASI%04X index %d\n",
		pao->type, pao->index);

	if (phw->pCache)
		phw->pCache->adap_idx = pao->index;

	return 0;
}

int HPI6000_adapter_init(struct hpi_adapter_obj *pao)
{
	int err = _HPI6000_adapter_init(pao);

	if (err)
		AdapterShutdown(pao);
	return err;
}

static void AdapterShutdown(struct hpi_adapter_obj *pao)
{
	struct hpi_hw_obj *phw = pao->priv;

	// reset DSPs on adapter
	HPIOS_MEMWRITE32(phw->dw2040_HPICSR + HPI_RESET, 0x0003000F);

	if (pao->wHasControlCache)
		HpiFreeControlCache(phw->pCache);

	HpiOs_MemFree(phw);
}

/************************************************************************/
// ADAPTER
static void AdapterDelete(struct hpi_adapter_obj *pao,
	struct hpi_message *phm,
	struct hpi_response *phr
)
{
	(void)phm;

	AdapterShutdown(pao);
	phr->wError = 0;
}

static void AdapterGetAsserts(
	struct hpi_adapter_obj *pao,
	struct hpi_message *phm,
	struct hpi_response *phr
)
{
#ifdef GENERATE_PCI_ASSERTS
	// if we have PCI2040 asserts then collect them
	if ((gwPciReadAsserts > 0) || (gwPciWriteAsserts > 0)) {
		phr->u.ax.assert.p1 =
			gwPciReadAsserts * 100 + gwPciWriteAsserts;
		phr->u.ax.assert.p2 = 0;
		phr->u.ax.assert.count = 1;	// assert count
		phr->u.ax.assert.dsp_index = -1;	// "dsp index"
		strcpy(phr->u.ax.assert.szMessage, "PCI2040 error");
		phr->u.ax.assert.dsp_msg_addr = 0;
		gwPciReadAsserts = 0;
		gwPciWriteAsserts = 0;
		phr->wError = 0;
	} else
#endif
		HwMessage(pao, phm, phr);	//get DSP asserts

	return;
}

/************************************************************************/
// LOW-LEVEL

static short Hpi6000_AdapterBootLoadDsp(struct hpi_adapter_obj *pao)
{
	struct hpi_hw_obj *phw = pao->priv;
	short nError;
	uint32_t dwTimeout;
	uint32_t dwRead = 0;
	uint32_t i = 0;
	uint32_t dwData = 0;
	uint32_t j = 0;
	uint32_t dwTestAddr = 0x80000000;
	uint32_t dwTestData = 0x00000001;
	uint32_t dw2040Reset = 0;
	uint32_t dwDspIndex = 0;
	uint32_t dwEndian = 0;
	uint32_t dwAdapterInfo = 0;
	uint32_t dwDelay = 0;

	struct dsp_code DspCode;
	uint16_t nBootLoadFamily = 0;

	/* NOTE don't use wAdapterType in this routine. It is not setup yet */

	switch (HPIOS_PCI_SUBSYS_DEVICE(pao)) {
	case 0x5100:
	case 0x5110:		// ASI5100 revB or higher with C6711D
	case 0x5200:		// ASI5200 PCIe version of ASI5100
	case 0x6100:
	case 0x6200:
		nBootLoadFamily = HPI_ADAPTER_FAMILY_ASI(0x6200);
		break;
	default:
		HPI_DEBUG_LOG1(ERROR, "SubSys ID %04x not recognised\n",
			       HPIOS_PCI_SUBSYS_DEVICE(pao));
		return HPI_ERROR_DSP_BOOTLOAD;
	}

	/* reset all DSPs, indicate two DSPs are present
	 * set RST3-=1 to disconnect HAD8 to set DSP in little endian mode
	 */
	dwEndian = 0;
	dw2040Reset = 0x0003000F;
	HPIOS_MEMWRITE32(phw->dw2040_HPICSR + HPI_RESET, dw2040Reset);

	/* read back register to make sure PCI2040 chip is functioning
	 * note that bits 4..15 are read-only and so should always return zero,
	 * even though we wrote 1 to them
	 */
	HpiOs_DelayMicroSeconds(1000);
	dwDelay = HPIOS_MEMREAD32(phw->dw2040_HPICSR + HPI_RESET);

	if (dwDelay != dw2040Reset) {
		HPI_DEBUG_LOG2(ERROR, "INIT_PCI2040 %x %x\n",
			dw2040Reset, dwDelay);
		return HPI_ERROR_DSP_HARDWARE;
	}

	/* Indicate that DSP#0,1 is a C6X */
	HPIOS_MEMWRITE32(phw->dw2040_HPICSR + HPI_DATA_WIDTH, 0x00000003);
	/* set Bit30 and 29 - which will prevent Target aborts from being
	 * issued upon HPI or GP error
	 */
	HPIOS_MEMWRITE32(phw->dw2040_HPICSR + INTERRUPT_MASK_SET, 0x60000000);

	/* isolate DSP HAD8 line from PCI2040 so that
	 * Little endian can be set by pullup
	 */
	dw2040Reset = dw2040Reset & (~(dwEndian << 3));
	HPIOS_MEMWRITE32(phw->dw2040_HPICSR + HPI_RESET, dw2040Reset);

	phw->ado[0].cDspRev = 'B';	// revB
	phw->ado[1].cDspRev = 'B';	// revB

	/*Take both DSPs out of reset, setting HAD8 to the correct Endian */
	dw2040Reset = dw2040Reset & (~0x00000001);	/* start DSP 0 */
	HPIOS_MEMWRITE32(phw->dw2040_HPICSR + HPI_RESET, dw2040Reset);
	dw2040Reset = dw2040Reset & (~0x00000002);	/* start DSP 1 */
	HPIOS_MEMWRITE32(phw->dw2040_HPICSR + HPI_RESET, dw2040Reset);

	/* set HAD8 back to PCI2040, now that DSP set to little endian mode */
	dw2040Reset = dw2040Reset & (~0x00000008);
	HPIOS_MEMWRITE32(phw->dw2040_HPICSR + HPI_RESET, dw2040Reset);
	//delay to allow DSP to get going
	HpiOs_DelayMicroSeconds(100);

	// loop through all DSPs, downloading DSP code
	for (dwDspIndex = 0; dwDspIndex < phw->wNumDsp; dwDspIndex++) {
		struct dsp_obj *pdo = &phw->ado[dwDspIndex];

		// configure DSP so that we download code into the SRAM
		// set control reg for little endian, HWOB=1
		HPIOS_MEMWRITE32(pdo->prHPIControl, 0x00010001);

		// test access to the HPI address register (HPIA)
		dwTestData = 0x00000001;
		for (j = 0; j < 32; j++) {
			HPIOS_MEMWRITE32(pdo->prHPIAddress, dwTestData);
			dwData = HPIOS_MEMREAD32(pdo->prHPIAddress);
			if (dwData != dwTestData) {
				HPI_DEBUG_LOG3(ERROR,
					"INIT_DSPHPI %x %x %x\n",
					dwTestData, dwData, dwDspIndex);
				return HPI_ERROR_DSP_HARDWARE;
			}
			dwTestData = dwTestData << 1;
		}

/* if C6713 the setup PLL to generate 225MHz from 25MHz.
* Since the PLLDIV1 read is sometimes wrong, even on a C6713,
* we're going to do this unconditionally
*/
/* PLLDIV1 should have a value of 8000 after reset */
/*
	if (HpiReadWord(pdo,0x01B7C118) == 0x8000)
*/
		{
			/* C6713 datasheet says we cannot program PLL from HPI,
			 * and indeed if we try to set the PLL multiply from the
			 * HPI, the PLL does not seem to lock,
			 * so we enable the PLL and use the default of x 7
			 */
			/* bypass PLL */
			HpiWriteWord(pdo, 0x01B7C100, 0x0000);
			HpiOs_DelayMicroSeconds(100);


			//  ** use default of PLL  x7 **
			// EMIF = 225/3=75MHz
			HpiWriteWord(pdo, 0x01B7C120, 0x8002);
			HpiOs_DelayMicroSeconds(100);

			// peri = 225/2
			HpiWriteWord(pdo, 0x01B7C11C, 0x8001);
			HpiOs_DelayMicroSeconds(100);

			// cpu  = 225/1
			HpiWriteWord(pdo, 0x01B7C118, 0x8000);

			/* ~2ms delay */
			HpiOs_DelayMicroSeconds(2000);

			// PLL not bypassed
			HpiWriteWord(pdo, 0x01B7C100, 0x0001);
			/* ~2ms delay */
			HpiOs_DelayMicroSeconds(2000);
		}

		/* test r/w to internal DSP memory
		 * C6711 has L2 cache mapped to 0x0 when reset
		 *
		 *  revB - because of bug 3.0.1 last HPI read
		 * (before HPI address issued) must be non-autoinc
		 */
		/* test each bit in the 32bit word */
		for (i = 0; i < 100; i++) {
			dwTestAddr = 0x00000000;
			dwTestData = 0x00000001;
			for (j = 0; j < 32; j++) {
				HpiWriteWord(pdo, dwTestAddr + i, dwTestData);
				dwData = HpiReadWord(pdo, dwTestAddr + i);
				if (dwData != dwTestData) {
					HPI_DEBUG_LOG4(ERROR,
						"DSP mem %x %x %x %x\n",
						dwTestAddr + i,
						dwTestData, dwData,
						dwDspIndex);

					return HPI_ERROR_DSP_HARDWARE;
				}
				dwTestData = dwTestData << 1;
			}
		}

		/* memory map of ASI6200
		   00000000-0000FFFF    16Kx32 internal program
		   01800000-019FFFFF    Internal peripheral
		   80000000-807FFFFF    CE0 2Mx32 SDRAM running @ 100MHz
		   90000000-9000FFFF    CE1 Async peripherals:

		   EMIF config
		   ------------
		   Global EMIF control
		   0 -
		   1 -
		   2 -
		   3 CLK2EN = 1   CLKOUT2 enabled
		   4 CLK1EN = 0   CLKOUT1 disabled
		   5 EKEN = 1 <--!! C6713 specific, enables ECLKOUT
		   6 -
		   7 NOHOLD = 1   external HOLD disabled
		   8 HOLDA = 0    HOLDA output is low
		   9 HOLD = 0             HOLD input is low
		   10 ARDY = 1    ARDY input is high
		   11 BUSREQ = 0   BUSREQ output is low
		   12,13 Reserved = 1
		 */
		HpiWriteWord(pdo, 0x01800000, 0x34A8);

		/* EMIF CE0 setup - 2Mx32 Sync DRAM
		   31..28       Wr setup
		   27..22       Wr strobe
		   21..20       Wr hold
		   19..16       Rd setup
		   15..14       -
		   13..8        Rd strobe
		   7..4         MTYPE   0011            Sync DRAM 32bits
		   3            Wr hold MSB
		   2..0         Rd hold
		 */
		HpiWriteWord(pdo, 0x01800008, 0x00000030);

		/* EMIF SDRAM Extension
		   31-21        0
		   20           WR2RD = 0
		   19-18        WR2DEAC = 1
		   17           WR2WR = 0
		   16-15        R2WDQM = 2
		   14-12        RD2WR = 4
		   11-10        RD2DEAC = 1
		   9            RD2RD = 1
		   8-7          THZP = 10b
		   6-5          TWR  = 2-1 = 01b (tWR = 10ns)
		   4            TRRD = 0b = 2 ECLK (tRRD = 14ns)
		   3-1          TRAS = 5-1 = 100b (Tras=42ns = 5 ECLK)
		   1            CAS latency = 3 ECLK
		   (for Micron 2M32-7 operating at 100Mhz)
		 */

		// need to use this else DSP code crashes
		HpiWriteWord(pdo, 0x01800020, 0x001BDF29);

		/* EMIF SDRAM control - set up for a 2Mx32 SDRAM (512x32x4 bank)
		   31           -               -
		   30           SDBSZ   1               4 bank
		   29..28       SDRSZ   00              11 row address pins
		   27..26       SDCSZ   01              8 column address pins
		   25           RFEN    1               refersh enabled
		   24           INIT    1               init SDRAM
		   23..20       TRCD    0001
		   19..16       TRP             0001
		   15..12       TRC             0110
		   11..0        -               -
		 */
		//      need to use this else DSP code crashes
		HpiWriteWord(pdo, 0x01800018, 0x47117000);

		// EMIF SDRAM Refresh Timing
		HpiWriteWord(pdo, 0x0180001C, 0x00000410);

		/*MIF CE1 setup - Async peripherals
		   @100MHz bus speed, each cycle is 10ns,
		   31..28       Wr setup  = 1
		   27..22       Wr strobe = 3                   30ns
		   21..20       Wr hold = 1
		   19..16       Rd setup =1
		   15..14       Ta = 2
		   13..8        Rd strobe = 3                   30ns
		   7..4         MTYPE   0010            Async 32bits
		   3            Wr hold MSB =0
		   2..0         Rd hold = 1
		 */
		{
			uint32_t dwCE1 = (1L << 28) | (3L << 22) | (1L << 20) |
				(1L << 16) | (2L << 14) | (3L << 8) |
				(2L << 4) | 1L;
			HpiWriteWord(pdo, 0x01800004, dwCE1);
		}

		// delay a little to allow SDRAM and DSP to "get going"
		HpiOs_DelayMicroSeconds(1000);

		// test access to SDRAM
		{
			dwTestAddr = 0x80000000;
			dwTestData = 0x00000001;
			// test each bit in the 32bit word
			for (j = 0; j < 32; j++) {
				HpiWriteWord(pdo, dwTestAddr, dwTestData);
				dwData = HpiReadWord(pdo, dwTestAddr);
				if (dwData != dwTestData) {
					HPI_DEBUG_LOG4(ERROR,
						"DSP dram %x %x %x %x\n",
						dwTestAddr,
						dwTestData, dwData,
						dwDspIndex);

					return HPI_ERROR_DSP_HARDWARE;
				}
				dwTestData = dwTestData << 1;
			}
			// test every Nth address in the DRAM
#define DRAM_SIZE_WORDS 0x200000	//2Mx32
#define DRAM_INC 1024
			dwTestAddr = 0x80000000;
			dwTestData = 0x0;
			for (i = 0; i < DRAM_SIZE_WORDS; i = i + DRAM_INC) {
				HpiWriteWord(pdo, dwTestAddr + i, dwTestData);
				dwTestData++;
			}
			dwTestAddr = 0x80000000;
			dwTestData = 0x0;
			for (i = 0; i < DRAM_SIZE_WORDS; i = i + DRAM_INC) {
				dwData = HpiReadWord(pdo, dwTestAddr + i);
				if (dwData != dwTestData) {
					HPI_DEBUG_LOG4(ERROR,
						"DSP dram %x %x %x %x\n",
						dwTestAddr + i,
						dwTestData, dwData,
						dwDspIndex);
					return HPI_ERROR_DSP_HARDWARE;
				}
				dwTestData++;
			}

		}

		// write the DSP code down into the DSPs memory
		nError = HpiDspCode_Open(nBootLoadFamily, &pao->os,
							&DspCode);

		if (nError)
			return nError;

		while (1) {
			uint32_t dwLength;
			uint32_t dwAddress;
			uint32_t dwType;
			uint32_t *pdwCode;

			nError = HpiDspCode_ReadWord(&DspCode, &dwLength);
			if (nError)
				break;
			if (dwLength == 0xFFFFFFFF)
				break;	// end of code

			nError = HpiDspCode_ReadWord(&DspCode, &dwAddress);
			if (nError)
				break;
			nError = HpiDspCode_ReadWord(&DspCode, &dwType);
			if (nError)
				break;
			nError = HpiDspCode_ReadBlock(dwLength, &DspCode,
				&pdwCode);
			if (nError)
				break;
			nError = Hpi6000_DspBlockWrite32(pao,
				(uint16_t)dwDspIndex, dwAddress, pdwCode,
				dwLength);
			if (nError)
				break;
		}

		if (nError) {
			HpiDspCode_Close(&DspCode);
			return nError;
		}
		// verify that code was written correctly
		// this time through, assume no errors in DSP code file/array
		HpiDspCode_Rewind(&DspCode);
		while (1) {
			uint32_t dwLength;
			uint32_t dwAddress;
			uint32_t dwType;
			uint32_t *pdwCode;

			HpiDspCode_ReadWord(&DspCode, &dwLength);
			if (dwLength == 0xFFFFFFFF)
				break;	// end of code

			HpiDspCode_ReadWord(&DspCode, &dwAddress);
			HpiDspCode_ReadWord(&DspCode, &dwType);
			HpiDspCode_ReadBlock(dwLength, &DspCode, &pdwCode);

			for (i = 0; i < dwLength; i++) {
				dwData = HpiReadWord(pdo, dwAddress);
				if (dwData != *pdwCode) {
					nError = HPI_ERROR_DSP_HARDWARE;
					HPI_DEBUG_LOG4(ERROR,
						"DSP verify %x %x %x %x\n",
						dwAddress, *pdwCode,
						dwData, dwDspIndex);
					break;
				}
				pdwCode++;
				dwAddress += 4;
			}
			if (nError)
				break;
		}
		HpiDspCode_Close(&DspCode);
		if (nError)
			return nError;

		// zero out the hostmailbox
		{
			uint32_t dwAddress = HPI_HIF_ADDR(dwHostCmd);
			for (i = 0; i < 4; i++) {
				HpiWriteWord(pdo, dwAddress, 0);
				dwAddress += 4;
			}
		}
		// write the DSP number into the hostmailbox
		// structure before starting the DSP
		HpiWriteWord(pdo, HPI_HIF_ADDR(dwDspNumber), dwDspIndex);

		// write the DSP adapter Info into the
		// hostmailbox before starting the DSP
		if (dwDspIndex > 0)
			HpiWriteWord(pdo, HPI_HIF_ADDR(dwAdapterInfo),
				dwAdapterInfo);

		// step 3. Start code by sending interrupt
		HPIOS_MEMWRITE32(pdo->prHPIControl, 0x00030003);
		HpiOs_DelayMicroSeconds(10000);


		/* wait for a non-zero value in hostcmd -
		 * indicating initialization is complete
		 *
		 * Init could take a while if DSP checks SDRAM memory
		 * Was 200000. Increased to 2000000 for ASI8801 so we
		 * don't get 938 errors.
		 */
		dwTimeout = 2000000;
		while (dwTimeout) {
			do {
				dwRead = HpiReadWord(pdo,
					HPI_HIF_ADDR(dwHostCmd));
			} while (--dwTimeout
				&& Hpi6000_Check_PCI2040_ErrorFlag(pao,
					H6READ));

			if (dwRead)
				break;
			/* The following is a workaround for bug #94:
			 * Bluescreen on install and subsequent boots on a
			 * DELL PowerEdge 600SC PC with 1.8GHz P4 and
			 * ServerWorks chipset. Without this delay the system
			 * locks up with a bluescreen (NOT GPF or pagefault).
			 */
			else
				HpiOs_DelayMicroSeconds(10000);
		}
		if (dwTimeout == 0) {
			HPI_DEBUG_LOG0(ERROR, "Timed out waiting for DSP ack\n");
			return HPI_ERROR_DSP_COMMUNICATION;
		}

		// read the DSP adapter Info from the
		// hostmailbox structure after starting the DSP
		if (dwDspIndex == 0) {
			//uint32_t dwTestData=0;
			uint32_t dwMask = 0;

			dwAdapterInfo =
				HpiReadWord(pdo, HPI_HIF_ADDR(dwAdapterInfo));
			if (HPI_ADAPTER_FAMILY_ASI(
				HPI_HIF_ADAPTER_INFO_EXTRACT_ADAPTER
					(dwAdapterInfo)) ==
				HPI_ADAPTER_FAMILY_ASI(0x6200))
				// all 6200 cards have this many DSPs
				phw->wNumDsp = 2;

			// test that the PLD is programmed
			// and we can read/write 24bits
#define PLD_BASE_ADDRESS 0x90000000L	//for ASI6100/6200/8800

			switch (nBootLoadFamily) {
			case HPI_ADAPTER_FAMILY_ASI(0x6200):
				// ASI6100/6200 has 24bit path to FPGA
				dwMask = 0xFFFFFF00L;
				// ASI5100 uses AX6 code,
				// but has no PLD r/w register to test
			if (HPI_ADAPTER_FAMILY_ASI(HPIOS_PCI_SUBSYS_DEVICE(pao)) ==
					HPI_ADAPTER_FAMILY_ASI(0x5100))
					dwMask = 0x00000000L;
				// ASI5200 uses AX6 code,
				// but has no PLD r/w register to test
				if (HPI_ADAPTER_FAMILY_ASI(HPIOS_PCI_SUBSYS_DEVICE(pao)) ==
					HPI_ADAPTER_FAMILY_ASI(0x5200))
					dwMask = 0x00000000L;
				break;
			case HPI_ADAPTER_FAMILY_ASI(0x8800):
				// ASI8800 has 16bit path to FPGA
				dwMask = 0xFFFF0000L;
				break;
			}
			dwTestData = 0xAAAAAA00L & dwMask;
			// write to 24 bit Debug register (D31-D8)
			HpiWriteWord(pdo, PLD_BASE_ADDRESS + 4L, dwTestData);
			dwRead = HpiReadWord(pdo,
				PLD_BASE_ADDRESS + 4L) & dwMask;
			if (dwRead != dwTestData) {
				HPI_DEBUG_LOG2(ERROR,
					"PLD %x %x\n", dwTestData, dwRead);
				return HPI_ERROR_DSP_HARDWARE;
			}
			dwTestData = 0x55555500L & dwMask;
			HpiWriteWord(pdo, PLD_BASE_ADDRESS + 4L, dwTestData);
			dwRead = HpiReadWord(pdo,
				PLD_BASE_ADDRESS + 4L) & dwMask;
			if (dwRead != dwTestData) {
				HPI_DEBUG_LOG2(ERROR,
					"PLD %x %x\n", dwTestData, dwRead);
				return HPI_ERROR_DSP_HARDWARE;
			}
		}
	}	// for wNumDSP
	return 0;
}

#define PCI_TIMEOUT 100

static int HpiSetAddress(
	struct dsp_obj *pdo,
	uint32_t dwAddress
)
{
	uint32_t dwTimeout = PCI_TIMEOUT;

	do {
		HPIOS_MEMWRITE32(pdo->prHPIAddress, dwAddress);
	} while (Hpi6000_Check_PCI2040_ErrorFlag(pdo->paParentAdapter, H6WRITE)
		&& --dwTimeout);

	if (dwTimeout)
		return 0;

	return 1;
}

// write one word to the HPI port
static void HpiWriteWord(
	struct dsp_obj *pdo,
	uint32_t dwAddress,
	uint32_t dwData
)
{
	if (HpiSetAddress(pdo, dwAddress))
		return;
	HPIOS_MEMWRITE32(pdo->prHPIData, dwData);
}

// read one word from the HPI port
static uint32_t HpiReadWord(
	struct dsp_obj *pdo,
	uint32_t dwAddress
)
{
	uint32_t dwData = 0;

	if (HpiSetAddress(pdo, dwAddress))
		return 0;	//? No way to return error

	// take care of errata in revB DSP (2.0.1)
	dwData = HPIOS_MEMREAD32(pdo->prHPIData);
	return dwData;
}

// write a block of 32bit words to the DSP HPI port using auto-inc mode
static void HpiWriteBlock(
	struct dsp_obj *pdo,
	uint32_t dwAddress,
	uint32_t *pdwData,
	uint32_t dwLength
)
{
	uint16_t wLength16 = (uint16_t)dwLength - 1;

	if (dwLength == 0)
		return;

	if (HpiSetAddress(pdo, dwAddress))
		return;

	HPIOS_MEMWRITEBLK32(pdo->prHPIDataAutoInc, pdwData, wLength16);

	// take care of errata in revB DSP (2.0.1)
	// must end with non auto-inc
	HPIOS_MEMWRITE32(pdo->prHPIData, *(pdwData + dwLength - 1));
}

/** read a block of 32bit words from the DSP HPI port using auto-inc mode
 */
static void HpiReadBlock(
	struct dsp_obj *pdo,
	uint32_t dwAddress,
	uint32_t *pdwData,
	uint32_t dwLength
)
{
	uint16_t wLength16 = (uint16_t)dwLength - 1;

	if (dwLength == 0)
		return;

	if (HpiSetAddress(pdo, dwAddress))
		return;

	HPIOS_MEMREADBLK32(pdo->prHPIDataAutoInc, pdwData, wLength16);

	// take care of errata in revB DSP (2.0.1)
	// must end with non auto-inc
	*(pdwData + dwLength - 1) = HPIOS_MEMREAD32(pdo->prHPIData);
}

static uint16_t Hpi6000_DspBlockWrite32(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	uint32_t dwHpiAddress,
	uint32_t *dwSource,
	uint32_t dwCount
)
{
	struct hpi_hw_obj *phw = pao->priv;
	struct dsp_obj *pdo = &phw->ado[wDspIndex];
	uint32_t dwTimeOut = PCI_TIMEOUT;
	int nC6711BurstSize = 128;
	uint32_t dwLocalHpiAddress = dwHpiAddress;
	int wLocalCount = dwCount;
	int wXferSize;
	uint32_t *pdwData = dwSource;

	while (wLocalCount) {
		if (wLocalCount > nC6711BurstSize)
			wXferSize = nC6711BurstSize;
		else
			wXferSize = wLocalCount;

		dwTimeOut = PCI_TIMEOUT;
		do {
			HpiWriteBlock(pdo, dwLocalHpiAddress, pdwData,
				wXferSize);
		} while (Hpi6000_Check_PCI2040_ErrorFlag(pao, H6WRITE)
			&& --dwTimeOut);

		if (!dwTimeOut)
			break;
		pdwData += wXferSize;
		dwLocalHpiAddress += sizeof(uint32_t) * wXferSize;
		wLocalCount -= wXferSize;
	}

	if (dwTimeOut)
		return 0;

	HPI_DEBUG_LOG0(ERROR, "DspBlockWrite32 failed\n");
	return 1;
}

static uint16_t Hpi6000_DspBlockRead32(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	uint32_t dwHpiAddress,
	uint32_t *dwDest,
	uint32_t dwCount
)
{
	struct hpi_hw_obj *phw = pao->priv;
	struct dsp_obj *pdo = &phw->ado[wDspIndex];
	uint32_t dwTimeOut = PCI_TIMEOUT;
	int nC6711BurstSize = 16;
	uint32_t dwLocalHpiAddress = dwHpiAddress;
	int wLocalCount = dwCount;
	int wXferSize;
	uint32_t *pdwData = dwDest;
	uint32_t dwLoopCount = 0;

	while (wLocalCount) {
		if (wLocalCount > nC6711BurstSize)
			wXferSize = nC6711BurstSize;
		else
			wXferSize = wLocalCount;

		dwTimeOut = PCI_TIMEOUT;
		do {
			HpiReadBlock(pdo, dwLocalHpiAddress, pdwData,
				wXferSize);
		} while (Hpi6000_Check_PCI2040_ErrorFlag(pao, H6READ)
			&& --dwTimeOut);
		if (!dwTimeOut)
			break;

		pdwData += wXferSize;
		dwLocalHpiAddress += sizeof(uint32_t) * wXferSize;
		wLocalCount -= wXferSize;
		dwLoopCount++;
	}

	if (dwTimeOut)
		return 0;

	HPI_DEBUG_LOG0(ERROR, "DspBlockRead32 failed\n");
	return 1;
}

#ifdef HPI_OS_WDM
char verify_error_buffer[128];
#endif

static short Hpi6000_MessageResponseSequence(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	struct hpi_message *phm,
	struct hpi_response *phr
)
{
	struct hpi_hw_obj *phw = pao->priv;
	struct dsp_obj *pdo = &phw->ado[wDspIndex];
	uint32_t dwTimeout;
	uint16_t wAck;
	uint32_t dwAddress;
	uint32_t dwLength;
	uint32_t *pData;

	wAck = Hpi6000_WaitDspAck(pao, wDspIndex, HPI_HIF_IDLE);
	if (wAck & HPI_HIF_ERROR_MASK) {
		pao->wDspCrashed++;
		goto error_exit;
	}
	pao->wDspCrashed = 0;

	// get the message address and size
	if (phw->dwMessageBufferAddressOnDsp == 0) {
		dwTimeout = TIMEOUT;
		do {
			dwAddress = HpiReadWord(pdo,
				HPI_HIF_ADDR(dwMessageBufferAddress));
			phw->dwMessageBufferAddressOnDsp = dwAddress;
		} while (Hpi6000_Check_PCI2040_ErrorFlag(pao, H6READ)
			&& --dwTimeout);
		if (!dwTimeout)
			goto error_exit;
	} else
		dwAddress = phw->dwMessageBufferAddressOnDsp;

	dwLength = phm->wSize;

	// send the message
	pData = (uint32_t *)phm;
	if (Hpi6000_DspBlockWrite32(pao, wDspIndex, dwAddress,
			pData, (uint16_t)dwLength / 4))
		goto error_exit;

	if (Hpi6000_SendHostCommand(pao, wDspIndex, HPI_HIF_GET_RESP))
		goto error_exit;

	Hpi6000_SendDspInterrupt(pdo);

	wAck = Hpi6000_WaitDspAck(pao, wDspIndex, HPI_HIF_GET_RESP);
	if (wAck & HPI_HIF_ERROR_MASK)
		goto error_exit;

	// get the response address (move this to init?)
	if (phw->dwResponseBufferAddressOnDsp == 0) {
		dwTimeout = TIMEOUT;
		do {
			dwAddress = HpiReadWord(pdo,
				HPI_HIF_ADDR(dwResponseBufferAddress));
		} while (Hpi6000_Check_PCI2040_ErrorFlag(pao, H6READ)
			&& --dwTimeout);
		phw->dwResponseBufferAddressOnDsp = dwAddress;

		if (!dwTimeout)
			goto error_exit;
	} else
		dwAddress = phw->dwResponseBufferAddressOnDsp;

	// read the length of the response back from the DSP
	dwTimeout = TIMEOUT;
	do {
		dwLength = HpiReadWord(pdo, HPI_HIF_ADDR(dwLength));
	} while (Hpi6000_Check_PCI2040_ErrorFlag(pao, H6READ) && --dwTimeout);
	if (!dwTimeout)
		goto error_exit;

	if (dwLength > phr->wSize)
		return HPI_ERROR_RESPONSE_BUFFER_TOO_SMALL;

	// get the response
	pData = (uint32_t *)phr;
	if (Hpi6000_DspBlockRead32
		(pao, wDspIndex, dwAddress, pData, (uint16_t)dwLength / 4))
		goto error_exit;

	// set i/f back to idle
	if (Hpi6000_SendHostCommand(pao, wDspIndex, HPI_HIF_IDLE))
		goto error_exit;

	Hpi6000_SendDspInterrupt(pdo);

	return HpiValidateResponse(phm, phr);

error_exit:
	HPI_DEBUG_LOG0(ERROR, "Hpi6000_MessageResponseSequence failed\n");
	return HPI_ERROR_DSP_COMMUNICATION;
}

// have to set up the below defines to match stuff in the MAP file

#define MSG_ADDRESS (HPI_HIF_BASE+0x18)
#define MSG_LENGTH 11
#define RESP_ADDRESS (HPI_HIF_BASE+0x44)
#define RESP_LENGTH 16
#define QUEUE_START  (HPI_HIF_BASE+0x88)
#define QUEUE_SIZE 0x8000

static short Hpi6000_SendData_CheckAdr(
	uint32_t dwAddress,
	uint32_t dwLengthInDwords
)
{
//#define CHECKING       // comment this line in to enable checking
#ifdef CHECKING
	if (dwAddress < (uint32_t)MSG_ADDRESS)
		return 0;
	if (dwAddress > (uint32_t)(QUEUE_START + QUEUE_SIZE))
		return 0;
	if ((dwAddress + (dwLengthInDwords << 2)) >
		(uint32_t)(QUEUE_START + QUEUE_SIZE))
		return 0;
#else
	(void)dwAddress;
	(void)dwLengthInDwords;
	return 1;
#endif
}

static short Hpi6000_SendData(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	struct hpi_message *phm,
	struct hpi_response *phr
)
{
	struct hpi_hw_obj *phw = pao->priv;
	struct dsp_obj *pdo = &phw->ado[wDspIndex];
	uint32_t dwDataSent = 0;
	uint16_t wAck;
	uint32_t dwLength, dwAddress;
	uint32_t *pData = (uint32_t *)phm->u.d.u.data.pbData;
	uint16_t wTimeOut = 8;

	(void)phr;

	// round dwDataSize down to nearest 4 bytes
	while ((dwDataSent < (phm->u.d.u.data.dwDataSize & ~3L))
		&& --wTimeOut) {
		wAck = Hpi6000_WaitDspAck(pao, wDspIndex, HPI_HIF_IDLE);
		if (wAck & HPI_HIF_ERROR_MASK)
			goto error_exit;

		if (Hpi6000_SendHostCommand(pao, wDspIndex, HPI_HIF_SEND_DATA))
			goto error_exit;

		Hpi6000_SendDspInterrupt(pdo);

		wAck = Hpi6000_WaitDspAck(pao, wDspIndex, HPI_HIF_SEND_DATA);

		if (wAck & HPI_HIF_ERROR_MASK)
			goto error_exit;

		do {
			// get the address and size
			dwAddress = HpiReadWord(pdo, HPI_HIF_ADDR(dwAddress));
			// DSP returns number of DWORDS
			dwLength = HpiReadWord(pdo, HPI_HIF_ADDR(dwLength));
		} while (Hpi6000_Check_PCI2040_ErrorFlag(pao, H6READ));

		if (!Hpi6000_SendData_CheckAdr(dwAddress, dwLength))
			goto error_exit;

		/* send the data. break data into 512 DWORD blocks (2K bytes)
		 * and send using block write. 2Kbytes is the max as this is the
		 * memory window given to the HPI data register by the PCI2040
		 */

		{
			uint32_t dwLen = dwLength;
			uint32_t dwBlkLen = 512;
			while (dwLen) {
				if (dwLen < dwBlkLen)
					dwBlkLen = dwLen;
				if (Hpi6000_DspBlockWrite32(pao, wDspIndex,
						dwAddress, pData, dwBlkLen))
					goto error_exit;
				dwAddress += dwBlkLen * 4;
				pData += dwBlkLen;
				dwLen -= dwBlkLen;
			}
		}

		if (Hpi6000_SendHostCommand(pao, wDspIndex, HPI_HIF_IDLE))
			goto error_exit;

		Hpi6000_SendDspInterrupt(pdo);

		dwDataSent += dwLength * 4;
	}

	if (wTimeOut)
		return 0;
error_exit:
	HPI_DEBUG_LOG0(ERROR, "Hpi6000_SendData failed\n");
	return HPI_ERROR_DSP_COMMUNICATION;
}

static short Hpi6000_GetData(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	struct hpi_message *phm,
	struct hpi_response *phr
)
{
	struct hpi_hw_obj *phw = pao->priv;
	struct dsp_obj *pdo = &phw->ado[wDspIndex];
	uint32_t dwDataGot = 0;
	uint16_t wAck;
	uint32_t dwLength, dwAddress;
	uint32_t *pData = (uint32_t *)phm->u.d.u.data.pbData;

	(void)phr;	// this parameter not used!

	// round dwDataSize down to nearest 4 bytes
	while (dwDataGot < (phm->u.d.u.data.dwDataSize & ~3L)) {
		wAck = Hpi6000_WaitDspAck(pao, wDspIndex, HPI_HIF_IDLE);
		if (wAck & HPI_HIF_ERROR_MASK)
			goto error_exit;

		if (Hpi6000_SendHostCommand(pao, wDspIndex, HPI_HIF_GET_DATA))
			goto error_exit;
		Hpi6000_SendDspInterrupt(pdo);

		wAck = Hpi6000_WaitDspAck(pao, wDspIndex, HPI_HIF_GET_DATA);

		if (wAck & HPI_HIF_ERROR_MASK)
			goto error_exit;

		// get the address and size
		do {
			dwAddress = HpiReadWord(pdo, HPI_HIF_ADDR(dwAddress));
			dwLength = HpiReadWord(pdo, HPI_HIF_ADDR(dwLength));
		} while (Hpi6000_Check_PCI2040_ErrorFlag(pao, H6READ));

		// read the data
		{
			uint32_t dwLen = dwLength;
			uint32_t dwBlkLen = 512;
			while (dwLen) {
				if (dwLen < dwBlkLen)
					dwBlkLen = dwLen;
				if (Hpi6000_DspBlockRead32(pao, wDspIndex,
						dwAddress, pData, dwBlkLen))
					goto error_exit;
				dwAddress += dwBlkLen * 4;
				pData += dwBlkLen;
				dwLen -= dwBlkLen;
			}
		}

		if (Hpi6000_SendHostCommand(pao, wDspIndex, HPI_HIF_IDLE))
			goto error_exit;
		Hpi6000_SendDspInterrupt(pdo);

		dwDataGot += dwLength * 4;
	}

	return 0;

error_exit:
	HPI_DEBUG_LOG0(ERROR, "Hpi6000_GetData failed\n");
	return HPI_ERROR_DSP_COMMUNICATION;
}

static void Hpi6000_SendDspInterrupt(
	struct dsp_obj *pdo
)
{
	HPIOS_MEMWRITE32(pdo->prHPIControl, 0x00030003);	// DSPINT
}

static short Hpi6000_SendHostCommand(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	uint32_t dwHostCmd
)
{
	struct hpi_hw_obj *phw = pao->priv;
	struct dsp_obj *pdo = &phw->ado[wDspIndex];
	uint32_t dwTimeout = TIMEOUT;

	// set command
	do {
		HpiWriteWord(pdo, HPI_HIF_ADDR(dwHostCmd), dwHostCmd);
		// flush the FIFO
		HpiSetAddress(pdo, HPI_HIF_ADDR(dwHostCmd));
	} while (Hpi6000_Check_PCI2040_ErrorFlag(pao, H6WRITE) && --dwTimeout);

	// reset the interrupt bit
	HPIOS_MEMWRITE32(pdo->prHPIControl, 0x00040004);

	if (dwTimeout)
		return 0;

	HPI_DEBUG_LOG1(ERROR, "SendHostCommand %d timed out\n", dwHostCmd);
	return 1;
}

#ifdef HPI6000_ENABLE_BACKDOOR_READ
// This is a backdoor method of reading the HPI error count on a particular
// PCI2040. This fn should be declared in say WHPI.C and called directly.
// It is a backdoor into this module and should only be used for test purposes.
long Hpi6000_BackDoor_Read_PCI2040_ErrorFlagCount(
	uint16_t wAdapter
)
{
	if (wAdapter < MAX_ADAPTERS)
		return gao60[wAdapter].dwPCI2040HPIErrorCount;
	else
		return 0;
}
#endif

// if the PCI2040 has recorded an HPI timeout, reset the error and return 1
static short Hpi6000_Check_PCI2040_ErrorFlag(
	struct hpi_adapter_obj *pao,
	uint16_t nReadOrWrite
)
{
	uint32_t dwHPIError;

	struct hpi_hw_obj *phw = pao->priv;

	// read the error bits from the PCI2040
	dwHPIError = HPIOS_MEMREAD32(phw->dw2040_HPICSR + HPI_ERROR_REPORT);
	if (dwHPIError) {
		// reset the error flag
		HPIOS_MEMWRITE32(phw->dw2040_HPICSR + HPI_ERROR_REPORT, 0L);
		phw->dwPCI2040HPIErrorCount++;
		if (nReadOrWrite == 1)
			gwPciReadAsserts++;	/************* inc global */
		else
			gwPciWriteAsserts++;
		return 1;
	} else
		return 0;
}

static short Hpi6000_WaitDspAck(
	struct hpi_adapter_obj *pao,
	uint16_t wDspIndex,
	uint32_t dwAckValue
)
{
	struct hpi_hw_obj *phw = pao->priv;
	struct dsp_obj *pdo = &phw->ado[wDspIndex];
	uint32_t dwAck = 0L;
	uint32_t dwTimeout;
	uint32_t dwHPIC = 0L;

	// wait for host interrupt to signal ack is ready
	dwTimeout = TIMEOUT;
	while (--dwTimeout) {
		dwHPIC = HPIOS_MEMREAD32(pdo->prHPIControl);
		if (dwHPIC & 0x04)	// 0x04 = HINT from DSP
			break;
	}
	if (dwTimeout == 0) {
		HPI_DEBUG_LOG0(ERROR, "WaitDspAck no HINT");
		return HPI_HIF_ERROR_MASK;
	}

	// wait for dwAckValue
	dwTimeout = TIMEOUT;
	while (--dwTimeout) {
		// read the ack mailbox
		dwAck = HpiReadWord(pdo, HPI_HIF_ADDR(dwDspAck));
		if (dwAck == dwAckValue)
			break;
		if ((dwAck & HPI_HIF_ERROR_MASK) &&
			!Hpi6000_Check_PCI2040_ErrorFlag(pao, H6READ))
			break;
		//for (i=0;i<1000;i++)
		//      dwPause=i+1;
	}
	if (dwAck & HPI_HIF_ERROR_MASK)
		/* indicates bad read from DSP -
		   typically 0xffffff is read for some reason */
		dwAck = HPI_HIF_ERROR_MASK;

	if (dwTimeout == 0) {
		HPI_DEBUG_LOG1(ERROR, "WaitDspAck(%d) timed out", dwAckValue);
		dwAck = HPI_HIF_ERROR_MASK;
	}
	return (short)dwAck;
}

static short Hpi6000_UpdateControlCache(
	struct hpi_adapter_obj *pao,
	struct hpi_message *phm
)
{
	const uint16_t wDspIndex = 0;
	struct hpi_hw_obj *phw = pao->priv;
	struct dsp_obj *pdo = &phw->ado[wDspIndex];
	uint32_t dwTimeout;
	uint32_t dwCacheDirtyFlag;
	uint16_t err;

	(void)phm;
	(void)pao;

	HpiOs_SpinLock_Lock(&pao->dspLock);

	dwTimeout = TIMEOUT;
	do {
		dwCacheDirtyFlag = HpiReadWord((struct dsp_obj *)pdo,
			HPI_HIF_ADDR(dwControlCacheIsDirty));
	} while (Hpi6000_Check_PCI2040_ErrorFlag(pao, H6READ) && --dwTimeout);
	if (!dwTimeout) {
		HPI_DEBUG_LOG0(ERROR, "Control cache flag read failed\n");
		err = HPI_ERROR_CONTROL_CACHING;
		goto unlock;
	}

	if (dwCacheDirtyFlag) {
		// read the cached controls
		uint32_t dwAddress;
		uint32_t dwLength;

		dwTimeout = TIMEOUT;
		if (pdo->dwControlCacheAddressOnDsp == 0) {
			do {
				dwAddress =
					HpiReadWord((struct dsp_obj *)pdo,
					HPI_HIF_ADDR(dwControlCacheAddress));

				dwLength =
					HpiReadWord((struct dsp_obj *)pdo,
					HPI_HIF_ADDR
					(dwControlCacheSizeInBytes));
			} while (Hpi6000_Check_PCI2040_ErrorFlag(pao, H6READ)
				&& --dwTimeout);
			if (!dwTimeout) {
				HPI_DEBUG_LOG0(ERROR, "Control cache address read failed\n");
				err = HPI_ERROR_CONTROL_CACHING;
				goto unlock;
			}
			pdo->dwControlCacheAddressOnDsp = dwAddress;
			pdo->dwControlCacheLengthOnDsp = dwLength;
		} else {
			dwAddress = pdo->dwControlCacheAddressOnDsp;
			dwLength = pdo->dwControlCacheLengthOnDsp;
		}

		if (Hpi6000_DspBlockRead32(pao, wDspIndex, dwAddress,
				(uint32_t *)&phw->aControlCache[0],
				dwLength / sizeof(uint32_t))) {
			HPI_DEBUG_LOG0(ERROR, "Control cache read failed\n");
			err = HPI_ERROR_CONTROL_CACHING;
			goto unlock;
		}
		do {
			HpiWriteWord((struct dsp_obj *)pdo,
				HPI_HIF_ADDR(dwControlCacheIsDirty), 0);
			// flush the FIFO
			HpiSetAddress(pdo, HPI_HIF_ADDR(dwHostCmd));
		} while (Hpi6000_Check_PCI2040_ErrorFlag(pao, H6WRITE)
			&& --dwTimeout);
		if (!dwTimeout) {
			HPI_DEBUG_LOG0(ERROR, "Control cache flush failed\n");
			err = HPI_ERROR_CONTROL_CACHING;
			goto unlock;
		}

	}
	err = 0;

unlock:
	HpiOs_SpinLock_Unlock(&pao->dspLock);
	return err;
}

/** Get dsp index for multi DSP adapters only */
static uint16_t GetDspIndex(
	struct hpi_adapter_obj *pao,
	struct hpi_message *phm)
{
	uint16_t ret = 0;
	(void)pao;
	
	switch (phm->wObject) {
	case HPI_OBJ_ISTREAM:
		if   (phm->wObjIndex < 2)
			ret = 1;
		break;
	case HPI_OBJ_PROFILE:
		ret = phm->wObjIndex;
		break;
	default:
		break;
	}
	return ret;
}

/** Complete transaction with DSP

Send message, get response, send or get stream data if any.
*/
static void HwMessage(
	struct hpi_adapter_obj *pao,
	struct hpi_message *phm,
	struct hpi_response *phr
)
{
	uint16_t nError = 0;
	uint16_t wDspIndex = 0;
	struct hpi_hw_obj *phw = pao->priv;
	uint16_t  numDsp = phw->wNumDsp;

	if (numDsp < 2)
		wDspIndex = 0;
	else {
		wDspIndex = GetDspIndex(pao, phm);

		/* is this  checked on the DSP anyway? */
		if ((phm->wFunction == HPI_ISTREAM_GROUP_ADD) ||
			(phm->wFunction == HPI_OSTREAM_GROUP_ADD)) {
			struct hpi_message hm;
			uint16_t wAddIndex;
			hm.wObjIndex = phm->u.d.u.stream.wStreamIndex;
			hm.wObject = phm->u.d.u.stream.wObjectType;
			wAddIndex = GetDspIndex(pao, &hm);
			if (wAddIndex != wDspIndex) {
				phr->wError = HPI_ERROR_NO_INTERDSP_GROUPS;
				return;
			}
		}
	}

	HpiOs_SpinLock_Lock(&pao->dspLock);
	nError = Hpi6000_MessageResponseSequence(pao, wDspIndex, phm, phr);

	if (nError) /* something failed in the HPI/DSP interface */
		goto err;

	if (phr->wError)	// something failed in the DSP
		goto out;

	switch (phm->wFunction) {
	case HPI_OSTREAM_WRITE:
	case HPI_ISTREAM_ANC_WRITE:
		nError = Hpi6000_SendData(pao, wDspIndex, phm, phr);
		break;
	case HPI_ISTREAM_READ:
	case HPI_OSTREAM_ANC_READ:
		nError = Hpi6000_GetData(pao, wDspIndex, phm, phr);
		break;
	case HPI_ADAPTER_GET_ASSERT:
		phr->u.ax.assert.dsp_index = 0;	// dsp 0 default
		if (numDsp == 2) {
			if (!phr->u.ax.assert.count) {
				// no assert from dsp 0, check dsp 1
				nError = Hpi6000_MessageResponseSequence(pao,
				         1, phm, phr);
				phr->u.ax.assert.dsp_index = 1;
			}
		}
	}

err:
	if (nError) {
		phr->wError = nError;
		// just the header of the response is valid
		phr->wSize = sizeof(struct hpi_response_header);
	}
out:
	HpiOs_SpinLock_Unlock(&pao->dspLock);
	return;
}
