/*
 * Common routines for sddr09.c and a few other drivers.
 * Unrelated to CF/SM - just USB stuff.
 */

/*
 * Send a control message and wait for the response.
 *
 * us - the pointer to the us_data structure for the device to use
 *
 * request - the URB Setup Packet's first 6 bytes. The first byte always
 *  corresponds to the request type, and the second byte always corresponds
 *  to the request.  The other 4 bytes do not correspond to value and index,
 *  since they are used in a custom way by the SCM protocol.
 *
 * xfer_data - a buffer from which to get, or to which to store, any data
 *  that gets send or received, respectively, with the URB. Even though
 *  it looks like we allocate a buffer in this code for the data, xfer_data
 *  must contain enough allocated space.
 *
 * xfer_len - the number of bytes to send or receive with the URB.
 *
 */
#include "debug.h"
#include "transport.h"
#include "raw_bulk.h"

/* #ifdef CONFIG_USB_STORAGE_DEBUG */
#if 1
#define DEBUG_PRCT 12
#else
#define DEBUG_PRCT 0
#endif

/* #define US_DEBUGP	printk */

int
usb_storage_send_control(struct us_data *us,
			 int pipe,
			 unsigned char request,
			 unsigned char requesttype,
			 unsigned int value,
			 unsigned int index,
			 unsigned char *xfer_data,
			 unsigned int xfer_len) {

	int result;

	// Send the URB to the device and wait for a response.

	/* Why are request and request type reversed in this call? */

	result = usb_stor_control_msg(us, pipe,
			request, requesttype, value, index,
			xfer_data, xfer_len);


	// Check the return code for the command.

	if (result < 0) {
		/* if the command was aborted, indicate that */
		if (result == -ENOENT)
			return USB_STOR_TRANSPORT_ABORTED;

		/* a stall is a fatal condition from the device */
		if (result == -EPIPE) {
			US_DEBUGP("-- Stall on control pipe. Clearing\n");
			result = usb_clear_halt(us->pusb_dev, pipe);
			US_DEBUGP("-- usb_clear_halt() returns %d\n", result);
			return USB_STOR_TRANSPORT_FAILED;
		}

		/* Uh oh... serious problem here */
		return USB_STOR_TRANSPORT_ERROR;
	}

	return USB_STOR_TRANSPORT_GOOD;
}

int
usb_storage_raw_bulk(struct us_data *us, int direction, unsigned char *data,
		     unsigned int len, unsigned int *act_len) {

	int result;
	int pipe;

	if (direction == SCSI_DATA_READ)
		pipe = usb_rcvbulkpipe(us->pusb_dev, us->ep_in);
	else
		pipe = usb_sndbulkpipe(us->pusb_dev, us->ep_out);

	result = usb_stor_bulk_msg(us, data, pipe, len, act_len);

	/* if we stall, we need to clear it before we go on */
	if (result == -EPIPE) {
		US_DEBUGP("EPIPE: clearing endpoint halt for"
			  " pipe 0x%x, stalled at %d bytes\n",
			  pipe, *act_len);
		usb_clear_halt(us->pusb_dev, pipe);
		/* return US_BULK_TRANSFER_SHORT; */
	}

	if (result) {
		/* -ENOENT -- we canceled this transfer */
		if (result == -ENOENT) {
			US_DEBUGP("raw_bulk(): transfer aborted\n");
			return US_BULK_TRANSFER_ABORTED;
		}

		/* NAK - that means we've retried a few times already */
       		if (result == -ETIMEDOUT)
			US_DEBUGP("raw_bulk(): device NAKed\n");
		else if (result == -EOVERFLOW)
			US_DEBUGP("raw_bulk(): babble/overflow\n");
		else if (result == -ECONNRESET)
			US_DEBUGP("raw_bulk(): asynchronous reset\n");
		else if (result != -EPIPE)
			US_DEBUGP("raw_bulk(): unknown error %d\n",
				  result);

		return US_BULK_TRANSFER_FAILED;
	}

	if (*act_len != len) {
		US_DEBUGP("Warning: Transferred only %d of %d bytes\n",
			  *act_len, len);
		return US_BULK_TRANSFER_SHORT;
	}

#if 0
	US_DEBUGP("raw_bulk(): Transferred %s %d of %d bytes\n",
		  (direction == SCSI_DATA_READ) ? "in" : "out",
		  *act_len, len);
#endif

	return US_BULK_TRANSFER_GOOD;
}

int
usb_storage_bulk_transport(struct us_data *us, int direction,
			   unsigned char *data, unsigned int len, int use_sg) {

	int result = USB_STOR_TRANSPORT_GOOD;
	int transferred = 0;
	int i;
	struct scatterlist *sg;
	unsigned int act_len;

	if (len == 0)
		return USB_STOR_TRANSPORT_GOOD;

#if DEBUG_PRCT

	if (direction == SCSI_DATA_WRITE && !use_sg) {
		char string[64];

		/* Debug-print the first N bytes of the write transfer */

		strcpy(string, "wr: ");
		for (i=0; i<len && i<DEBUG_PRCT; i++) {
			sprintf(string+strlen(string), "%02X ",
				data[i]);
			if ((i%16) == 15) {
				US_DEBUGP("%s\n", string);
				strcpy(string, "wr: ");
			}
		}
		if ((i%16)!=0)
			US_DEBUGP("%s\n", string);
	}

	US_DEBUGP("SCM data %s transfer %d sg buffers %d\n",
		  (direction == SCSI_DATA_READ) ? "in" : "out",
		  len, use_sg);

#endif /* DEBUG_PRCT */

	if (!use_sg)
		result = usb_storage_raw_bulk(us, direction,
					      data, len, &act_len);
	else {
		sg = (struct scatterlist *)data;

		for (i=0; i<use_sg && transferred<len; i++) {
			unsigned char *buf;
			unsigned int length;

			buf = page_address(sg[i].page) + sg[i].offset;
			length = len-transferred;
			if (length > sg[i].length)
				length = sg[i].length;

			result = usb_storage_raw_bulk(us, direction,
						      buf, length, &act_len);
			if (result != US_BULK_TRANSFER_GOOD)
				break;
			transferred += sg[i].length;
		}
	}

#if DEBUG_PRCT

	if (direction == SCSI_DATA_READ && !use_sg) {
		char string[64];

		/* Debug-print the first N bytes of the read transfer */

		strcpy(string, "rd: ");
		for (i=0; i<len && i<act_len && i<DEBUG_PRCT; i++) {
			sprintf(string+strlen(string), "%02X ",
				data[i]);
			if ((i%16) == 15) {
				US_DEBUGP("%s\n", string);
				strcpy(string, "rd: ");
			}
		}
		if ((i%16)!=0)
			US_DEBUGP("%s\n", string);
	}

#endif /* DEBUG_PRCT */

	return result;
}
