/*
 * SmartMedia driver for Datafab/Apacer SM-CF reader - aeb, bevrijdingsdag 2002
 * (The CF part lives in datafab.c.)
 *
 * It is said that this device is based on the OnSpec 90C36-LC1 chip.
 */

/*
 * Commands: 8 bytes
 * Five commands are known:
 *
 * Identify:      0 0 0 0 0 b0 0 80
 *
 * Read DeviceID: 0 0 0 0 0 b0 0 84
 *
 * Read:          0 P P P 0 b0 N 85
 * here N is the number of 256-byte chunks,
 * the high order bits of PPP are the PBA, the low order bits
 * the sector within an erase block.
 *
 * Write:         L1 P P P eN b0 L2 86
 * here e is the bit "no evacuation needed" (bit 6), and
 * N is the number of 512-byte sectors (bits 5-0),
 * and PPP is as for Read, and L1,L2 are the low and high order
 * parts of LBA.
 *
 * The write is not done at the indicated address, but elsewhere,
 * and after the 2-byte OK status, four more bytes are returned
 * to indicate where. The address PPP is big-endian, the returned
 * address little-endian.
 *
 * Read map:      0 0 0 0 0 b0 L 8a
 * here L is the number of 256-byte chunks.
 * The map in 2 bytes (little endian) per block.
 *
 * (The CF part uses Identify: 00 01 00 00 00 a0 ec 01.)
 */

#include <linux/config.h>
#include "transport.h"
#include "protocol.h"
#include "usb.h"
#include "raw_bulk.h"
#include "apacer.h"
#include "datafab.h"
#include "smartmedia.h"
#include "debug.h"

/* #define US_DEBUGP       printk */

/*
 * Several commands give a 2-byte status that should be read.
 * Good status is 50 00.
 * Failure (e.g. no media) is FF 04.
 */
static int
apacer_read_status(struct us_data *us) {
	unsigned char status[2];
	int result;

	result = usb_storage_bulk_transport(us, SCSI_DATA_READ,
					    status, sizeof(status), 0);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return result;

	if (status[0] == 0x50 && status[1] == 0)
		return USB_STOR_TRANSPORT_GOOD;

	if (status[0] != 0xff || status[1] != 0x04)
		US_DEBUGP("apacer_read_status: got status %x %x\n",
			  status[0], status[1]);

	return USB_STOR_TRANSPORT_ERROR;
}

/*
 * Read status (2 bytes) and newpba (4 bytes) after a write.
 * good status is 50 00
 * bad status is ff 04
 * four more bytes are 0 P3 P2 P1, where PPP is the little-endian
 * version of the new PBA.
 */
static int
apacer_read6(struct us_data *us, unsigned int *newpba) {
	struct sm_card_info *info = (struct sm_card_info *) us->extra;
	unsigned char status[6];
	unsigned int address, blockmask;
	int result;

	result = usb_storage_bulk_transport(us, SCSI_DATA_READ,
					    status, sizeof(status), 0);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return result;

	address = ((u32)status[5] << 16) + ((u32)status[4] << 8) + status[3];

	*newpba = (address >> info->blockshift);

	blockmask = (1 << info->blockshift) - 1;
	if ((status[3] & blockmask) || status[2])
		printk("apacer_read6: unexpected status return\n");

	US_DEBUGP("apacer_read6: %02X %02X %02X %02X - newpba %d\n",
	       status[2], status[3], status[4], status[5], *newpba);

	if (status[0] == 0x50 && status[1] == 0)
		return USB_STOR_TRANSPORT_GOOD;

	if (status[0] != 0xff || status[1] != 0x04)
		US_DEBUGP("apacer_read6: got status %x %x\n",
			  status[0], status[1]);

	return USB_STOR_TRANSPORT_ERROR;
}

/*
 * This is used as a test for device present
 * Expected result:
 * Either
 *  4 bytes 0b * * *, followed by 2 bytes 50 00: device present
 * or
 *  2 bytes ff 04: no device, or device not ready
 */
static int
apacer_identify(struct us_data *us) {
	unsigned char command[8] = { 0, 0, 0, 0, 0, 0xb0, 0, 0x80 };
	unsigned char identity[4];
	int result;

	result = usb_storage_bulk_transport(us, SCSI_DATA_WRITE,
					    command, sizeof(command), 0);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return result;

	result = usb_storage_bulk_transport(us, SCSI_DATA_READ,
					    identity, sizeof(identity), 0);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return result;

	US_DEBUGP("apacer_identify: %x %x %x %x\n",
	       identity[0], identity[1], identity[2], identity[3]);
	// 0b 02 04 00 for an 8MB card (in 2 readers)
	// 0b 20 00 00 for the same card (in Sitecom reader)

	return apacer_read_status(us);
}

static int
apacer_get_wp(struct us_data *us) {
//	struct sm_card_info *info = (struct sm_card_info *) us->extra;

//	info->flags |= SMARTMEDIA_WP;   /* write protected */
	return USB_STOR_TRANSPORT_GOOD;
}

// 98 e6 a5 9a for a 3V, 8 MB card
// ec 73 a5 bb for a 3V, 16MB card
static int
apacer_read_deviceID(struct us_data *us, unsigned char *deviceID) {
	unsigned char command[8] = { 0, 0, 0, 0, 0, 0xb0, 0, 0x84 };
	int result;

	result = usb_storage_bulk_transport(us, SCSI_DATA_WRITE,
					    command, sizeof(command), 0);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return result;

	result = usb_storage_bulk_transport(us, SCSI_DATA_READ,
					    deviceID, 4, 0);
	if (result != USB_STOR_TRANSPORT_GOOD) {
		printk("apacer.c: read ID failed\n");
		return result;
	}

	return apacer_read_status(us);
}

static int
apacer_do_read_map(struct us_data *us, unsigned char *buf, unsigned int len) {
	unsigned char command[8] = { 0, 0, 0, 0, 0, 0xb0, 0, 0x8a };
	int result;

	command[6] = (len >> 8);

	result = usb_storage_bulk_transport(us, SCSI_DATA_WRITE,
					    command, sizeof(command), 0);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return result;

	result = usb_storage_bulk_transport(us, SCSI_DATA_READ,
					    buf, len, 0);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return result;

	return apacer_read_status(us);
}

static int
apacer_read_map(struct us_data *us) {
	struct sm_card_info *info = (struct sm_card_info *) us->extra;
	int alloc_len, numblocks;
	int i, result;
	unsigned char *buf;
	unsigned int lba, lbact;

	if (!info->capacity)
		return -1;

	/* read 2 bytes for every block */
	alloc_len = info->capacity >>
		(info->blockshift + info->pageshift - 1);
	numblocks = alloc_len/2;

	buf = kmalloc(alloc_len, GFP_NOIO);
	if (buf == NULL || (alloc_len & 0xff) != 0)
		return 0;

	result = apacer_do_read_map(us, buf, alloc_len);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return 0;

	if (!smartmedia_allocate_map(info))
		return 0;

	lbact = 0;
	for (i = 0; i < numblocks; i++) {
		lba = buf[2*i] + (buf[2*i+1]<<8);
		if (lba < numblocks && lba < 1000) {
			lba += 1000*(i/0x400);
			if (info->lba_to_pba[lba] != UNDEF) {
				static int ct = 0;
				if (ct++ < 8)
					printk("apacer.c: Error: duplicate "
					       "LBA %d at %d and %d\n",
					       lba, info->lba_to_pba[lba], i);
			} else {
				info->lba_to_pba[lba] = i;
				info->pba_to_lba[i] = lba;
				lbact++;
			}
		} else if (lba == 1023) {	/* unused */
			lbact++;
		} else if (i > 1) {		/* not CIS */
			static int ct = 0;
			if (ct++ < 8)
				printk("apacer.c: Bad LBA %04X "
				       "for block %04X\n", lba, i);
		}
	}
	info->lbact = lbact;
	US_DEBUGP("Found %d LBA's\n", lbact);

	kfree(buf);
	return 0;
}

/* read from a single erase block; address and count in pages */
static int
apacer_read_data(struct us_data *us, unsigned long address,
		 unsigned int count, unsigned char *buf, int use_sg) {
	struct sm_card_info *info = (struct sm_card_info *) us->extra;
	unsigned char command[8] = { 0, 0, 0, 0, 0, 0xb0, 0, 0x85 };
	unsigned long bulklen;
	int result;

	if (use_sg) {
		printk("apacer.c: no sg today\n");
		return USB_STOR_TRANSPORT_ERROR;
	}
	if (count > 127) {
		printk("apacer.c: too many sectors\n");
		return USB_STOR_TRANSPORT_ERROR;
	}
	command[6] = count << (info->pageshift - 8);

	command[3] = (address & 0xff);
	command[2] = ((address >> 8) & 0xff);
	command[1] = ((address >> 16) & 0xff);

	result = usb_storage_bulk_transport(us, SCSI_DATA_WRITE,
					    command, sizeof(command), 0);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return result;

	bulklen = (count << info->pageshift);
	result = usb_storage_bulk_transport(us, SCSI_DATA_READ,
					    buf, bulklen, use_sg);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return result;

	return apacer_read_status(us);
}

static unsigned int
apacer_find_unused_pba(struct sm_card_info *info) {
	static unsigned int lastpba = 1;
	int numblocks = info->capacity >> (info->blockshift + info->pageshift);
	int i;

	for (i = lastpba+1; i < numblocks; i++) {
		if (info->pba_to_lba[i] == UNDEF) {
			lastpba = i;
			return i;
		}
	}
	for (i = 2; i <= lastpba; i++) {
		if (info->pba_to_lba[i] == UNDEF) {
			lastpba = i;
			return i;
		}
	}
	return 0;
}

static int
apacer_write_lba(struct us_data *us, unsigned int lba,
		 unsigned int page, unsigned int pagect,
		 unsigned char *buf) {
	struct sm_card_info *info = (struct sm_card_info *) us->extra;
	unsigned char command[8] = { 0, 0, 0, 0, 0, 0xb0, 0, 0x86 };
	unsigned long address, bulklen;
	unsigned int pba, newpba;
	int use_sg = 0;
	int result;

	if (pagect > 63) {
		printk("apacer.c: too many sectors\n");
		return USB_STOR_TRANSPORT_ERROR;
	}

	command[0] = (lba & 0xff);
	command[6] = ((lba >> 8) & 0xff);
	command[4] = pagect;

	pba = info->lba_to_pba[lba];

	if (pba == UNDEF) {
		pba = apacer_find_unused_pba(info);
		if (!pba) {
			printk("apacer_write_lba: Out of unused blocks\n");
			return USB_STOR_TRANSPORT_ERROR;
		}

		command[4] |= 0x40;
	}

	address = (pba << info->blockshift) + page;
	command[3] = (address & 0xff);
	command[2] = ((address >> 8) & 0xff);
	command[1] = ((address >> 16) & 0xff);

	result = usb_storage_bulk_transport(us, SCSI_DATA_WRITE,
					    command, sizeof(command), 0);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return result;

	bulklen = (pagect << info->pageshift);
	result = usb_storage_bulk_transport(us, SCSI_DATA_WRITE,
					    buf, bulklen, use_sg);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return result;

	result = apacer_read6(us, &newpba);
	if (result != USB_STOR_TRANSPORT_GOOD)
		return result;

	if ((command[4] & 0x40) != 0) {
		if (newpba != pba) {
			/* this is bad, since we lost sync;
			   should read map again */
			printk("apacer_write_lba: got newpba %d "
			       "instead of the expected %d\n",
			       newpba, pba);
			return USB_STOR_TRANSPORT_ERROR;
		}
		/* update map */
		info->pba_to_lba[pba] = lba;
		info->lba_to_pba[lba] = pba;
	} else {
		/* update map */
		info->pba_to_lba[pba] = UNDEF;
		info->pba_to_lba[newpba] = lba;
		info->lba_to_pba[lba] = newpba;
	}

	return USB_STOR_TRANSPORT_GOOD;
}

static int
apacer_test_unit_ready(struct us_data *us, Scsi_Cmnd *srb, int cmdlen) {
	return apacer_identify(us);
}

static struct sm_ops apacer_ops = {
	apacer_get_wp,
	apacer_read_deviceID,
	apacer_read_map,
	apacer_read_data,
	apacer_write_lba,
	apacer_test_unit_ready,
	NULL /* apacer_request_sense */
};

int
apacer_sm_transport(Scsi_Cmnd *srb, struct us_data *us) {
	return smartmedia_transport(srb, us, &apacer_ops);
}
