/*
 * Copyright (C) 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved.
 */

/*
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

/*!
 * @file sah_hardware_interface.c
 *
 * @brief Provides an interface to the SAHARA hardware registers.
 *
 */

/* SAHARA Includes */
#include <sah_driver_common.h>
#include <sah_hardware_interface.h>
#include <sah_memory_mapper.h>
#include <sah_kernel.h>

#if defined DIAG_DRV_IF || defined(DO_DBG)
#include <diagnostic.h>
#ifndef LOG_KDIAG
#define LOG_KDIAG(x) os_printk("%s\n", x)
#endif

static void sah_Dump_Link(const char *prefix, const sah_Link * link,
			  dma_addr_t addr);

/* This is for sprintf() to use when constructing output. */
#define DIAG_MSG_SIZE   1024
/* was 200 */
#define MAX_DUMP        200
static char Diag_msg[DIAG_MSG_SIZE];

#endif				/* DIAG_DRV_IF */

/*!
 * Number of descriptors sent to Sahara.  This value should only be updated
 * with the main queue lock held.
 */
uint32_t dar_count;

/* sahara virtual base address */
void *sah_virt_base;

/*! The "link-list optimize" bit in the Header of a Descriptor */
#define SAH_HDR_LLO 0x01000000

#define SAHARA_VERSION_REGISTER_OFFSET  0x000
#define SAHARA_DAR_REGISTER_OFFSET      0x004
#define SAHARA_CONTROL_REGISTER_OFFSET  0x008
#define SAHARA_COMMAND_REGISTER_OFFSET  0x00C
#define SAHARA_STATUS_REGISTER_OFFSET   0x010
#define SAHARA_ESTATUS_REGISTER_OFFSET  0x014
#define SAHARA_FLT_ADD_REGISTER_OFFSET  0x018
#define SAHARA_CDAR_REGISTER_OFFSET     0x01C
#define SAHARA_IDAR_REGISTER_OFFSET     0x020
#define SAHARA_OSTATUS_REGISTER_OFFSET  0x028
#define SAHARA_CONFIG_REGISTER_OFFSET   0x02C
#define SAHARA_MM_STAT_REGISTER_OFFSET  0x030


/* Local Functions */
#if defined DIAG_DRV_IF || defined DO_DBG
void sah_Dump_Region(const char *prefix, const unsigned char *data,
		     dma_addr_t addr, unsigned length);

#endif				/* DIAG_DRV_IF */

/* time out value when polling SAHARA status register for completion */
static uint32_t sah_poll_timeout = 0xFFFFFFFF;

/*!
 * Polls Sahara to determine when its current operation is complete
 *
 * @return   last value found in Sahara's status register
 */
sah_Execute_Status sah_Wait_On_Sahara()
{
	uint32_t count = 0;	/* ensure we don't get stuck in the loop forever */
	sah_Execute_Status status;	/* Sahara's status register */
	uint32_t stat_reg;

	pr_debug("Entered sah_Wait_On_Sahara\n");

	do {
		/* get current status register from Sahara */
		stat_reg = sah_HW_Read_Status();
		status = stat_reg & SAH_EXEC_STATE_MASK;

		/* timeout if SAHARA takes too long to complete */
		if (++count == sah_poll_timeout) {
			status = SAH_EXEC_FAULT;
			printk("sah_Wait_On_Sahara timed out\n");
		}

		/* stay in loop as long as Sahara is still busy */
	} while ((status == SAH_EXEC_BUSY) || (status == SAH_EXEC_DONE1_BUSY2));

	if (status == SAH_EXEC_ERROR1) {
		if (stat_reg & OP_STATUS) {
			status = SAH_EXEC_OPSTAT1;
		}
	}

	return status;
}				/* sah_Wait_on_Sahara() */

/*!
 * This function resets the SAHARA hardware. The following operations are
 * performed:
 *   1. Resets SAHARA.
 *   2. Requests BATCH mode.
 *   3. Enables interrupts.
 *   4. Requests Little Endian mode.
 *
 * @brief     SAHARA hardware reset function.
 *
 * @return   void
 */
int sah_HW_Reset(void)
{
	sah_Execute_Status sah_state;
	int status;		/* this is the value to return to the calling routine */
	uint32_t saha_control = 0;

#ifndef USE_3WORD_BURST
#ifdef FSL_HAVE_SAHARA2
	saha_control |= (8 << 16);	/* Allow 8-word burst */
#endif
#else
/***************** HARDWARE BUG WORK AROUND ******************/
/* A burst size of > 4 can cause Sahara DMA to issue invalid AHB transactions
 * when crossing 1KB boundaries.  By limiting the 'burst size' to 3, these
 * invalid transactions will not be generated, but Sahara will still transfer
 * data more efficiently than if the burst size were set to 1.
 */
	saha_control |= (3 << 16);	/* Limit DMA burst size. For versions 2/3 */
#endif				/* USE_3WORD_BURST */

#ifdef DIAG_DRV_IF
	snprintf(Diag_msg, DIAG_MSG_SIZE,
		 "Address of sah_virt_base = 0x%08x\n",
		 sah_virt_base);
	LOG_KDIAG(Diag_msg);
	snprintf(Diag_msg, DIAG_MSG_SIZE,
		 "Sahara Status register before reset: %08x",
		 sah_HW_Read_Status());
	LOG_KDIAG(Diag_msg);
#endif

	/* Write the Reset & BATCH mode command to the SAHARA Command register. */
	sah_HW_Write_Command(CMD_BATCH | CMD_RESET);
#ifdef SAHARA4_NO_USE_SQUIB
	{
		uint32_t cfg = sah_HW_Read_Config();
		cfg &= ~0x10000;
		sah_HW_Write_Config(cfg);
	}
#endif

	sah_poll_timeout = 0x0FFFFFFF;
	sah_state = sah_Wait_On_Sahara();
#ifdef DIAG_DRV_IF
	snprintf(Diag_msg, DIAG_MSG_SIZE,
		 "Sahara Status register after reset: %08x",
		 sah_HW_Read_Status());
	LOG_KDIAG(Diag_msg);
#endif
	/* on reset completion, check that Sahara is in the idle state */
	status = (sah_state == SAH_EXEC_IDLE) ? 0 : OS_ERROR_FAIL_S;

	/* Set initial value out of reset */
	sah_HW_Write_Control(saha_control);

#ifndef NO_RESEED_WORKAROUND
/***************** HARDWARE BUG WORK AROUND ******************/
/* In order to set the 'auto reseed' bit, must first acquire a random value. */
	/*
	 * to solve a hardware bug, a random number must be generated before
	 * the 'RNG Auto Reseed' bit can be set. So this generates a random
	 * number that is thrown away.
	 *
	 * Note that the interrupt bit has not been set at this point so
	 * the result can be polled.
	 */
#ifdef DIAG_DRV_IF
	LOG_KDIAG("Create and submit Random Number Descriptor");
#endif

	if (status == OS_ERROR_OK_S) {
		/* place to put random number */
		volatile uint32_t *random_data_ptr;
		sah_Head_Desc *random_desc;
		dma_addr_t desc_dma;
		dma_addr_t rand_dma;
		const int rnd_cnt = 3;	/* how many random 32-bit values to get */

		/* Get space for data -- assume at least 32-bit aligned! */
		random_data_ptr = os_alloc_memory(rnd_cnt * sizeof(uint32_t),
						  GFP_ATOMIC);

		random_desc = sah_Alloc_Head_Descriptor();

		if ((random_data_ptr == NULL) || (random_desc == NULL)) {
			status = OS_ERROR_FAIL_S;
		} else {
			int i;

			/* Clear out values */
			for (i = 0; i < rnd_cnt; i++) {
				random_data_ptr[i] = 0;
			}

			rand_dma = os_pa(random_data_ptr);

			random_desc->desc.header = 0xB18C0000;	/* LLO get random number */
			random_desc->desc.len1 =
			    rnd_cnt * sizeof(*random_data_ptr);
			random_desc->desc.ptr1 = (void *)rand_dma;
			random_desc->desc.original_ptr1 =
			    (void *)random_data_ptr;

			random_desc->desc.len2 = 0;	/* not used */
			random_desc->desc.ptr2 = 0;	/* not used */

			random_desc->desc.next = 0;	/* chain terminates here */
			random_desc->desc.original_next = 0;	/* chain terminates here */

			desc_dma = random_desc->desc.dma_addr;

			/* Force in-cache data out to RAM */
			os_cache_clean_range(random_data_ptr,
					     rnd_cnt *
					     sizeof(*random_data_ptr));

			/* pass descriptor to Sahara */
			sah_HW_Write_DAR(desc_dma);

			/*
			 * Wait for RNG to complete (interrupts are disabled at this point
			 * due to sahara being reset previously) then check for error
			 */
			sah_state = sah_Wait_On_Sahara();
			/* Force CPU to ignore in-cache and reload from RAM */
			os_cache_inv_range(random_data_ptr,
					   rnd_cnt * sizeof(*random_data_ptr));

			/* if it didn't move to done state, an error occured */
			if (
#ifndef SUBMIT_MULTIPLE_DARS
				   (sah_state != SAH_EXEC_IDLE) &&
#endif
				   (sah_state != SAH_EXEC_DONE1)
			    ) {
				status = OS_ERROR_FAIL_S;
				os_printk
				    ("(sahara) Failure: state is %08x; random_data is"
				     " %08x\n", sah_state, *random_data_ptr);
				os_printk
				    ("(sahara) CDAR: %08x, IDAR: %08x, FADR: %08x,"
				     " ESTAT: %08x\n", sah_HW_Read_CDAR(),
				     sah_HW_Read_IDAR(),
				     sah_HW_Read_Fault_Address(),
				     sah_HW_Read_Error_Status());
			} else {
				int i;
				int seen_rand = 0;

				for (i = 0; i < rnd_cnt; i++) {
					if (*random_data_ptr != 0) {
						seen_rand = 1;
						break;
					}
				}
				if (!seen_rand) {
					status = OS_ERROR_FAIL_S;
					os_printk
					    ("(sahara) Error:  Random number is zero!\n");
				}
			}
		}

		if (random_data_ptr) {
			os_free_memory((void *)random_data_ptr);
		}
		if (random_desc) {
			sah_Free_Head_Descriptor(random_desc);
		}
	}
/***************** END HARDWARE BUG WORK AROUND ******************/
#endif

	if (status == 0) {
#ifdef FSL_HAVE_SAHARA2
		saha_control |= CTRL_RNG_RESEED;
#endif

#ifndef SAHARA_POLL_MODE
		saha_control |= CTRL_INT_EN;	/* enable interrupts */
#else
		sah_poll_timeout = SAHARA_POLL_MODE_TIMEOUT;
#endif

#ifdef DIAG_DRV_IF
		snprintf(Diag_msg, DIAG_MSG_SIZE,
			 "Setting up Sahara's Control Register: %08x\n",
			 saha_control);
		LOG_KDIAG(Diag_msg);
#endif

		/* Rewrite the setup to the SAHARA Control register */
		sah_HW_Write_Control(saha_control);
#ifdef DIAG_DRV_IF
		snprintf(Diag_msg, DIAG_MSG_SIZE,
			 "Sahara Status register after control write: %08x",
			 sah_HW_Read_Status());
		LOG_KDIAG(Diag_msg);
#endif

#ifdef FSL_HAVE_SAHARA4
		{
			uint32_t cfg = sah_HW_Read_Config();
			sah_HW_Write_Config(cfg | 0x100);	/* Add RNG auto-reseed */
		}
#endif
	} else {
#ifdef DIAG_DRV_IF
		LOG_KDIAG("Reset failed\n");
#endif
	}

	return status;
}				/* sah_HW_Reset() */

/*!
 * This function enables High Assurance mode.
 *
 * @brief     SAHARA hardware enable High Assurance mode.
 *
 * @return   FSL_RETURN_OK_S             - if HA was set successfully
 * @return   FSL_RETURN_INTERNAL_ERROR_S - if HA was not set due to SAHARA
 *                                         being busy.
 */
fsl_shw_return_t sah_HW_Set_HA(void)
{
	/* This is the value to write to the register */
	uint32_t value;

	/* Read from the control register. */
	value = sah_HW_Read_Control();

	/* Set the HA bit */
	value |= CTRL_HA;

	/* Write to the control register. */
	sah_HW_Write_Control(value);

	/* Read from the control register. */
	value = sah_HW_Read_Control();

	return (value & CTRL_HA) ? FSL_RETURN_OK_S :
	    FSL_RETURN_INTERNAL_ERROR_S;
}

/*!
 * This function reads the SAHARA hardware Version Register.
 *
 * @brief     Read SAHARA hardware Version Register.
 *
 * @return   uint32_t Register value.
 */
uint32_t sah_HW_Read_Version(void)
{
	return os_read32(sah_virt_base + SAHARA_VERSION_REGISTER_OFFSET);
}

/*!
 * This function reads the SAHARA hardware Control Register.
 *
 * @brief     Read SAHARA hardware Control Register.
 *
 * @return   uint32_t Register value.
 */
uint32_t sah_HW_Read_Control(void)
{
	return os_read32(sah_virt_base + SAHARA_CONTROL_REGISTER_OFFSET);
}

/*!
 * This function reads the SAHARA hardware Status Register.
 *
 * @brief     Read SAHARA hardware Status Register.
 *
 * @return   uint32_t Register value.
 */
uint32_t sah_HW_Read_Status(void)
{
	return os_read32(sah_virt_base + SAHARA_STATUS_REGISTER_OFFSET);
}

/*!
 * This function reads the SAHARA hardware Error Status Register.
 *
 * @brief     Read SAHARA hardware Error Status Register.
 *
 * @return   uint32_t Error Status value.
 */
uint32_t sah_HW_Read_Error_Status(void)
{
	return os_read32(sah_virt_base + SAHARA_ESTATUS_REGISTER_OFFSET);
}

/*!
 * This function reads the SAHARA hardware Op Status Register.
 *
 * @brief     Read SAHARA hardware Op Status Register.
 *
 * @return   uint32_t Op Status value.
 */
uint32_t sah_HW_Read_Op_Status(void)
{
	return os_read32(sah_virt_base + SAHARA_OSTATUS_REGISTER_OFFSET);
}

/*!
 * This function reads the SAHARA hardware Descriptor Address Register.
 *
 * @brief     Read SAHARA hardware DAR Register.
 *
 * @return   uint32_t DAR value.
 */
uint32_t sah_HW_Read_DAR(void)
{
	return os_read32(sah_virt_base + SAHARA_DAR_REGISTER_OFFSET);
}

/*!
 * This function reads the SAHARA hardware Current Descriptor Address Register.
 *
 * @brief     Read SAHARA hardware CDAR Register.
 *
 * @return   uint32_t CDAR value.
 */
uint32_t sah_HW_Read_CDAR(void)
{
	return os_read32(sah_virt_base + SAHARA_CDAR_REGISTER_OFFSET);
}

/*!
 * This function reads the SAHARA hardware Initial Descriptor Address Register.
 *
 * @brief     Read SAHARA hardware IDAR Register.
 *
 * @return   uint32_t IDAR value.
 */
uint32_t sah_HW_Read_IDAR(void)
{
	return os_read32(sah_virt_base + SAHARA_IDAR_REGISTER_OFFSET);
}

/*!
 * This function reads the SAHARA hardware Fault Address Register.
 *
 * @brief     Read SAHARA Fault Address Register.
 *
 * @return   uint32_t Fault Address value.
 */
uint32_t sah_HW_Read_Fault_Address(void)
{
	return os_read32(sah_virt_base + SAHARA_FLT_ADD_REGISTER_OFFSET);
}

/*!
 * This function reads the SAHARA hardware Multiple Master Status Register.
 *
 * @brief     Read SAHARA hardware MM Stat Register.
 *
 * @return   uint32_t MM Stat value.
 */
uint32_t sah_HW_Read_MM_Status(void)
{
	return os_read32(sah_virt_base + SAHARA_MM_STAT_REGISTER_OFFSET);
}

/*!
 * This function reads the SAHARA hardware Configuration Register.
 *
 * @brief     Read SAHARA Configuration Register.
 *
 * @return   uint32_t Configuration value.
 */
uint32_t sah_HW_Read_Config(void)
{
	return os_read32(sah_virt_base + SAHARA_CONFIG_REGISTER_OFFSET);
}

/*!
 * This function writes a command to the SAHARA hardware Command Register.
 *
 * @brief     Write to SAHARA hardware Command Register.
 *
 * @param    command     An unsigned 32bit command value.
 *
 * @return   void
 */
void sah_HW_Write_Command(uint32_t command)
{
	os_write32(sah_virt_base + SAHARA_COMMAND_REGISTER_OFFSET, command);
}

/*!
 * This function writes a control value to the SAHARA hardware Control
 * Register.
 *
 * @brief     Write to SAHARA hardware Control Register.
 *
 * @param    control     An unsigned 32bit control value.
 *
 * @return   void
 */
void sah_HW_Write_Control(uint32_t control)
{
	os_write32(sah_virt_base + SAHARA_CONTROL_REGISTER_OFFSET, control);
}

/*!
 * This function writes a configuration value to the SAHARA hardware Configuration
 * Register.
 *
 * @brief     Write to SAHARA hardware Configuration Register.
 *
 * @param    configuration     An unsigned 32bit configuration value.
 *
 * @return   void
 */
void sah_HW_Write_Config(uint32_t configuration)
{
	os_write32(sah_virt_base + SAHARA_CONFIG_REGISTER_OFFSET,
		configuration);
}

/*!
 * This function writes a descriptor address to the SAHARA Descriptor Address
 * Register.
 *
 * @brief     Write to SAHARA Descriptor Address Register.
 *
 * @param    pointer     An unsigned 32bit descriptor address value.
 *
 * @return   void
 */
void sah_HW_Write_DAR(uint32_t pointer)
{
	os_write32(sah_virt_base + SAHARA_DAR_REGISTER_OFFSET, pointer);
	dar_count++;
}

#if defined DIAG_DRV_IF || defined DO_DBG

static char *interpret_header(uint32_t header)
{
	unsigned desc_type = ((header >> 24) & 0x70) | ((header >> 16) & 0xF);

	switch (desc_type) {
	case 0x12:
		return "5/SKHA_ST_CTX";
	case 0x13:
		return "35/SKHA_LD_MODE_KEY";
	case 0x14:
		return "38/SKHA_LD_MODE_IN_CPHR_ST_CTX";
	case 0x15:
		return "4/SKHA_IN_CPHR_OUT";
	case 0x16:
		return "34/SKHA_ST_SBOX";
	case 0x18:
		return "1/SKHA_LD_MODE_IV_KEY";
	case 0x19:
		return "33/SKHA_ST_SBOX";
	case 0x1D:
		return "2/SKHA_LD_MODE_IN_CPHR_OUT";
	case 0x22:
		return "11/MDHA_ST_MD";
	case 0x25:
		return "10/MDHA_HASH_ST_MD";
	case 0x28:
		return "6/MDHA_LD_MODE_MD_KEY";
	case 0x2A:
		return "39/MDHA_ICV";
	case 0x2D:
		return "8/MDHA_LD_MODE_HASH_ST_MD";
	case 0x3C:
		return "18/RNG_GEN";
	case 0x40:
		return "19/PKHA_LD_N_E";
	case 0x41:
		return "36/PKHA_LD_A3_B0";
	case 0x42:
		return "27/PKHA_ST_A_B";
	case 0x43:
		return "22/PKHA_LD_A_B";
	case 0x44:
		return "23/PKHA_LD_A0_A1";
	case 0x45:
		return "24/PKHA_LD_A2_A3";
	case 0x46:
		return "25/PKHA_LD_B0_B1";
	case 0x47:
		return "26/PKHA_LD_B2_B3";
	case 0x48:
		return "28/PKHA_ST_A0_A1";
	case 0x49:
		return "29/PKHA_ST_A2_A3";
	case 0x4A:
		return "30/PKHA_ST_B0_B1";
	case 0x4B:
		return "31/PKHA_ST_B2_B3";
	case 0x4C:
		return "32/PKHA_EX_ST_B1";
	case 0x4D:
		return "20/PKHA_LD_A_EX_ST_B";
	case 0x4E:
		return "21/PKHA_LD_N_EX_ST_B";
	case 0x4F:
		return "37/PKHA_ST_B1_B2";
	default:
		return "??/UNKNOWN";
	}
}				/* cvt_desc_name() */

/*!
 * Dump chain of descriptors to the log.
 *
 * @brief Dump descriptor chain
 *
 * @param    chain     Kernel virtual address of start of chain of descriptors
 *
 * @return   void
 */
void sah_Dump_Chain(const sah_Desc * chain, dma_addr_t addr)
{
	int desc_no = 1;

	pr_debug("Chain for Sahara\n");

	while (chain != NULL) {
		char desc_name[50];

		sprintf(desc_name, "Desc %02d (%s)\n" KERN_DEBUG "Desc  ",
			desc_no++, interpret_header(chain->header));

		sah_Dump_Words(desc_name, (unsigned *)chain, addr,
			       6 /* #words in h/w link */ );
		if (chain->original_ptr1) {
			if (chain->header & SAH_HDR_LLO) {
				sah_Dump_Region(" Data1",
						(unsigned char *)chain->
						original_ptr1,
						(dma_addr_t) chain->ptr1,
						chain->len1);
			} else {
				sah_Dump_Link(" Link1", chain->original_ptr1,
					      (dma_addr_t) chain->ptr1);
			}
		}
		if (chain->ptr2) {
			if (chain->header & SAH_HDR_LLO) {
				sah_Dump_Region(" Data2",
						(unsigned char *)chain->
						original_ptr2,
						(dma_addr_t) chain->ptr2,
						chain->len2);
			} else {
				sah_Dump_Link(" Link2", chain->original_ptr2,
					      (dma_addr_t) chain->ptr2);
			}
		}

		addr = (dma_addr_t) chain->next;
		chain = (chain->next) ? (chain->original_next) : NULL;
	}
}

/*!
 * Dump chain of links to the log.
 *
 * @brief Dump chain of links
 *
 * @param    prefix    Text to put in front of dumped data
 * @param    link      Kernel virtual address of start of chain of links
 *
 * @return   void
 */
static void sah_Dump_Link(const char *prefix, const sah_Link * link,
			  dma_addr_t addr)
{
#ifdef DUMP_SCC_DATA
	extern uint8_t *sahara_partition_base;
	extern dma_addr_t sahara_partition_phys;
#endif

	while (link != NULL) {
		sah_Dump_Words(prefix, (unsigned *)link, addr,
			       3 /* # words in h/w link */ );
		if (link->flags & SAH_STORED_KEY_INFO) {
#ifdef SAH_DUMP_DATA
#ifdef DUMP_SCC_DATA
			sah_Dump_Region("  Data",
					(uint8_t *) link->data -
					(uint8_t *) sahara_partition_phys +
					sahara_partition_base,
					(dma_addr_t) link->data, link->len);
#else
			pr_debug("  Key Slot %d\n", link->slot);
#endif
#endif
		} else {
#ifdef SAH_DUMP_DATA
			sah_Dump_Region("  Data", link->original_data,
					(dma_addr_t) link->data, link->len);
#endif
		}
		addr = (dma_addr_t) link->next;
		link = link->original_next;
	}
}

/*!
 * Dump given region of data to the log.
 *
 * @brief Dump data
 *
 * @param    prefix    Text to put in front of dumped data
 * @param    data      Kernel virtual address of start of region to dump
 * @param    length    Amount of data to dump
 *
 * @return   void
 */
void sah_Dump_Region(const char *prefix, const unsigned char *data,
		     dma_addr_t addr, unsigned length)
{
	unsigned count;
	char *output;
	unsigned data_len;

	sprintf(Diag_msg, "%s (%08X,%u):", prefix, addr, length);

	/* Restrict amount of data to dump */
	if (length > MAX_DUMP) {
		data_len = MAX_DUMP;
	} else {
		data_len = length;
	}

	/* We've already printed some text in output buffer, skip over it */
	output = Diag_msg + strlen(Diag_msg);

	for (count = 0; count < data_len; count++) {
		if ((count % 4) == 0) {
			*output++ = ' ';
		}
		sprintf(output, "%02X", *data++);
		output += 2;
	}

	pr_debug("%s\n", Diag_msg);
}

/*!
 * Dump given word of data to the log.
 *
 * @brief Dump data
 *
 * @param    prefix       Text to put in front of dumped data
 * @param    data         Kernel virtual address of start of region to dump
 * @param    word_count   Amount of data to dump
 *
 * @return   void
 */
void sah_Dump_Words(const char *prefix, const unsigned *data, dma_addr_t addr,
		    unsigned word_count)
{
	char *output;

	sprintf(Diag_msg, "%s (%08X,%uw): ", prefix, addr, word_count);

	/* We've already printed some text in output buffer, skip over it */
	output = Diag_msg + strlen(Diag_msg);

	while (word_count--) {
		sprintf(output, "%08X ", *data++);
		output += 9;
	}

	pr_debug("%s\n", Diag_msg);

}

#endif				/* DIAG_DRV_IF */

/* End of sah_hardware_interface.c */
