/*
 *	ultrastor.c	Copyright (C) 1992 David B. Gentzel
 *	Low-level SCSI driver for UltraStor 14F
 *	by David B. Gentzel, Whitfield Software Services, Carnegie, PA
 *	    (gentzel@nova.enet.dec.com)
 *  scatter/gather added by Scott Taylor (n217cg@tamuts.tamu.edu)
 *	Thanks to UltraStor for providing the necessary documentation
 */

/*
 * TODO:
 *	1. Cleanup error handling & reporting.
 *	2. Find out why scatter/gather is limited to 16 requests per command.
 *	3. Add multiple outstanding requests.
 *	4. See if we can make good use of having more than one command per lun.
 *	5. Test/improve/fix abort & reset functions.
 *	6. Look at command linking (mscp.command_link and
 *	   mscp.command_link_id).
 */

/*
 * NOTES:
 *    The UltraStor 14F is one of a family of intelligent, high performance
 *    SCSI-2 host adapters.  They all support command queueing and
 *    scatter/gather I/O.  Some of them can also emulate the standard
 *    WD1003 interface for use with OS's which don't support SCSI.
 *    Here is the scoop on the various models:
 *	14F - ISA first-party DMA HA with floppy support and WD1003 emulation.
 *	14N - ISA HA with floppy support.  I think that this is a non-DMA
 *	      HA.  Nothing further known.
 *	24F - EISA Bus Master HA with floppy support and WD1003 emulation.
 *	34F - VL-Bus Bus Master HA with floppy support (no WD1003 emulation).
 *
 *    The 14F is supported by this driver.  An effort has been made to support
 *    the 34F.  It should work, but is untested.  The 24F does not work at
 *    present.
 *
 *    Places flagged with a triple question-mark are things which are either
 *    unfinished, questionable, or wrong.
 */

#include <linux/stddef.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/kernel.h>

#include <asm/io.h>
#include <asm/system.h>
#include <asm/dma.h>

#define ULTRASTOR_PRIVATE	/* Get the private stuff from ultrastor.h */
#include "../blk.h"
#include "scsi.h"
#include "hosts.h"
#include "ultrastor.h"

#define ULTRASTOR_DEBUG 0

#define VERSION "1.1 alpha"

#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr)[0])
#define BYTE(num, n) ((unsigned char)((unsigned int)(num) >> ((n) * 8)))

/* Simply using "unsigned long" in these structures won't work as it causes
   alignment.  Perhaps the "aligned" attribute may be used in GCC 2.0 to get
   around this, but for now I use this hack. */
typedef struct {
    unsigned char bytes[4];
} Longword;

/* Used to fetch the configuration info from the config i/o registers.  We
   then store (in a friendlier format) in config. */
struct config_1 {
    unsigned char bios_segment: 3;
    unsigned char removable_disks_as_fixed: 1;
    unsigned char interrupt: 2;
    unsigned char dma_channel: 2;
};
struct config_2 {
    unsigned char ha_scsi_id: 3;
    unsigned char mapping_mode: 2;
    unsigned char bios_drive_number: 1;
    unsigned char tfr_port: 2;
};

/* Used to store configuration info read from config i/o registers.  Most of
   this is not used yet, but might as well save it. */
struct config {
    const void *bios_segment;
    unsigned short port_address;
    unsigned char interrupt: 4;
    unsigned char dma_channel: 3;
    unsigned char bios_drive_number: 1;
    unsigned char heads;
    unsigned char sectors;
    unsigned char ha_scsi_id: 3;
    unsigned char subversion: 4;
};

/* MailBox SCSI Command Packet.  Basic command structure for communicating
   with controller. */
struct mscp {
    unsigned char opcode: 3;		/* type of command */
    unsigned char xdir: 2;		/* data transfer direction */
    unsigned char dcn: 1;		/* disable disconnect */
    unsigned char ca: 1;		/* use cache (if available) */
    unsigned char sg: 1;		/* scatter/gather operation */
    unsigned char target_id: 3;		/* target SCSI id */
    unsigned char ch_no: 2;		/* SCSI channel (always 0 for 14f) */
    unsigned char lun: 3;		/* logical unit number */
    Longword transfer_data;		/* transfer data pointer */
    Longword transfer_data_length;	/* length in bytes */
    Longword command_link;		/* for linking command chains */
    unsigned char scsi_command_link_id;	/* identifies command in chain */
    unsigned char number_of_sg_list;	/* (if sg is set) 8 bytes per list */
    unsigned char length_of_sense_byte;
    unsigned char length_of_scsi_cdbs;	/* 6, 10, or 12 */
    unsigned char scsi_cdbs[12];	/* SCSI commands */
    unsigned char adapter_status;	/* non-zero indicates HA error */
    unsigned char target_status;	/* non-zero indicates target error */
    Longword sense_data;
};

/* The 14F uses an array of unaligned 4-byte ints for its scatter/gather list. */
typedef struct {
	unsigned long address;
	unsigned long num_bytes;
} ultrastor_sg_list;

/* This is our semaphore for mscp block availability */
int mscp_free = TRUE;

/* Allowed BIOS base addresses for 14f (NULL indicates reserved) */
static const void *const bios_segment_table_14f[8] = {
    NULL,	     (void *)0xC4000, (void *)0xC8000, (void *)0xCC000,
    (void *)0xD0000, (void *)0xD4000, (void *)0xD8000, (void *)0xDC000,
};

/* Allowed IRQs for 14f */
static const unsigned char interrupt_table_14f[4] = { 15, 14, 11, 10 };

/* Allowed DMA channels for 14f (0 indicates reserved) */
static const unsigned char dma_channel_table_14f[4] = { 5, 6, 7, 0 };

/* Head/sector mappings allowed by 14f */
static const struct {
    unsigned char heads;
    unsigned char sectors;
} mapping_table_14f[4] = { { 16, 63 }, { 64, 32 }, { 64, 63 }, { 0, 0 } };

/* Subversions of the 14F */
static const char *const subversion_names[] = { "14F", "34F" };

/* Config info */
static struct config config;

/* Our index in the host adapter array maintained by higher-level driver */
static int host_number;

/* PORT_ADDRESS is first port address used for i/o of messages. */
#ifdef PORT_OVERRIDE
# define PORT_ADDRESS PORT_OVERRIDE
#else
# define PORT_ADDRESS (config.port_address)
#endif

static volatile int aborted = 0;

#ifndef PORT_OVERRIDE
/* ??? A probe of address 0x310 screws up NE2000 cards */
static const unsigned short ultrastor_ports_14f[] = {
    0x330, 0x340, /*0x310,*/ 0x230, 0x240, 0x210, 0x130, 0x140,
};
#endif

static void ultrastor_interrupt(int cpl);
static inline void build_sg_list(Scsi_Cmnd *SCpnt);

static void (*ultrastor_done)(Scsi_Cmnd *) = 0;
static Scsi_Cmnd *SCint = NULL;

int ultrastor_detect(int hostnum)
{
    size_t i;
    unsigned char in_byte, version_byte = 0;
    struct config_1 config_1;
    struct config_2 config_2;

#if (ULTRASTOR_DEBUG & UD_DETECT)
    printk("US14F: detect: called\n");
#endif

#ifndef PORT_OVERRIDE
    PORT_ADDRESS = 0;
    for (i = 0; i < ARRAY_SIZE(ultrastor_ports_14f); i++) {
	PORT_ADDRESS = ultrastor_ports_14f[i];
#endif

#if (ULTRASTOR_DEBUG & UD_DETECT)
	printk("US14F: detect: testing port address %03X\n", PORT_ADDRESS);
#endif

	in_byte = inb(PRODUCT_ID(PORT_ADDRESS + 0));
	if (in_byte != US14F_PRODUCT_ID_0) {
#if (ULTRASTOR_DEBUG & UD_DETECT)
# ifdef PORT_OVERRIDE
	    printk("US14F: detect: wrong product ID 0 - %02X\n", in_byte);
# else
	    printk("US14F: detect: no adapter at port %03X\n", PORT_ADDRESS);
# endif
#endif
#ifdef PORT_OVERRIDE
	    return FALSE;
#else
	    continue;
#endif
	}
	in_byte = inb(PRODUCT_ID(PORT_ADDRESS + 1));
	/* Only upper nibble is significant for Product ID 1 */
	if ((in_byte & 0xF0) != US14F_PRODUCT_ID_1) {
#if (ULTRASTOR_DEBUG & UD_DETECT)
# ifdef PORT_OVERRIDE
	    printk("US14F: detect: wrong product ID 1 - %02X\n", in_byte);
# else
	    printk("US14F: detect: no adapter at port %03X\n", PORT_ADDRESS);
# endif
#endif
#ifdef PORT_OVERRIDE
	    return FALSE;
#else
	    continue;
#endif
	}
	version_byte = in_byte;
#ifndef PORT_OVERRIDE
	break;
    }
    if (i == ARRAY_SIZE(ultrastor_ports_14f)) {
# if (ULTRASTOR_DEBUG & UD_DETECT)
	printk("US14F: detect: no port address found!\n");
# endif
	return FALSE;
    }
#endif

#if (ULTRASTOR_DEBUG & UD_DETECT)
    printk("US14F: detect: adapter found at port address %03X\n",
	   PORT_ADDRESS);
#endif

    /* All above tests passed, must be the right thing.  Get some useful
       info. */
    *(char *)&config_1 = inb(CONFIG(PORT_ADDRESS + 0));
    *(char *)&config_2 = inb(CONFIG(PORT_ADDRESS + 1));
    config.bios_segment = bios_segment_table_14f[config_1.bios_segment];
    config.interrupt = interrupt_table_14f[config_1.interrupt];
    config.ha_scsi_id = config_2.ha_scsi_id;
    config.heads = mapping_table_14f[config_2.mapping_mode].heads;
    config.sectors = mapping_table_14f[config_2.mapping_mode].sectors;
    config.bios_drive_number = config_2.bios_drive_number;
    config.subversion = (version_byte & 0x0F);
    if (config.subversion == U34F)
	config.dma_channel = 0;
    else
	config.dma_channel = dma_channel_table_14f[config_1.dma_channel];

    if (!config.bios_segment) {
#if (ULTRASTOR_DEBUG & UD_DETECT)
	printk("US14F: detect: not detected.\n");
#endif
	return FALSE;
    }

    /* Final consistancy check, verify previous info. */
    if (config.subversion != U34F)
	if (!config.dma_channel || !(config_2.tfr_port & 0x2)) {
#if (ULTRASTOR_DEBUG & UD_DETECT)
	    printk("US14F: detect: consistancy check failed\n");
#endif
	    return FALSE;
	}

    /* If we were TRULY paranoid, we could issue a host adapter inquiry
       command here and verify the data returned.  But frankly, I'm
       exhausted! */

    /* Finally!  Now I'm satisfied... */
#if (ULTRASTOR_DEBUG & UD_DETECT)
    printk("US14F: detect: detect succeeded\n"
	   "  Port address: %03X\n"
	   "  BIOS segment: %05X\n"
	   "  Interrupt: %u\n"
	   "  DMA channel: %u\n"
	   "  H/A SCSI ID: %u\n"
	   "  Subversion: %u\n",
	   PORT_ADDRESS, config.bios_segment, config.interrupt,
	   config.dma_channel, config.ha_scsi_id, config.subversion);
#endif
    host_number = hostnum;
    scsi_hosts[hostnum].this_id = config.ha_scsi_id;
    scsi_hosts[hostnum].unchecked_isa_dma = (config.subversion != U34F);

    if (request_irq(config.interrupt, ultrastor_interrupt)) {
	printk("Unable to allocate IRQ%u for UltraStor controller.\n",
	       config.interrupt);
	return FALSE;
    }
    if (config.dma_channel && request_dma(config.dma_channel)) {
	printk("Unable to allocate DMA channel %u for UltraStor controller.\n",
	       config.dma_channel);
	free_irq(config.interrupt);
	return FALSE;
    }
	scsi_hosts[hostnum].sg_tablesize = ULTRASTOR_14F_MAX_SG;
	printk("UltraStor: scatter/gather enabled.  Using %d SG lists.\n", ULTRASTOR_14F_MAX_SG);

    return TRUE;
}

const char *ultrastor_info(void)
{
    static char buf[64];

    (void)sprintf(buf, "UltraStor %s SCSI @ Port %03X BIOS %05X IRQ%u DMA%u\n",
		  ((config.subversion < ARRAY_SIZE(subversion_names))
		   ? subversion_names[config.subversion] : "14F?"),
		  PORT_ADDRESS, (int)config.bios_segment, config.interrupt,
		  config.dma_channel);
    return buf;
}

static struct mscp mscp = {
    OP_SCSI, DTD_SCSI, 0, 1, 0		/* This stuff doesn't change */
};

static inline void build_sg_list(Scsi_Cmnd *SCpnt)
{
	ultrastor_sg_list *sglist;
	struct scatterlist *sl;
	long transfer_length = 0;
	int i;

	sl = (struct scatterlist *) SCpnt->request_buffer;
	SCpnt->host_scribble = (unsigned char *) scsi_malloc(512);
	if (SCpnt->host_scribble == NULL)
		/* Not sure what to do here; just panic for now */
		panic("US14F: Can't allocate DMA buffer for scatter-gather list!\n");
	/* Save ourselves some casts; can eliminate when we don't have to look at it anymore! */
	sglist = (ultrastor_sg_list *) SCpnt->host_scribble;
	for (i = 0; i < SCpnt->use_sg; i++) {
		sglist[i].address = sl[i].address;
		sglist[i].num_bytes = sl[i].length;
		transfer_length += sl[i].length;
	}
	mscp.number_of_sg_list = (char) SCpnt->use_sg;
	mscp.transfer_data = *(Longword *)&sglist;
	/* ??? May not be necessary.  Docs are unclear as to whether transfer length field is */
	/* ignored or whether it should be set to the total number of bytes of the transfer.  */
	mscp.transfer_data_length = *(Longword *)&transfer_length;
}

int ultrastor_queuecommand(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *))
{
    unsigned char in_byte;

#if (ULTRASTOR_DEBUG & UD_COMMAND)
    printk("US14F: queuecommand: called\n");
#endif

	/* We want to be sure that a command queued while another command   */
	/* is running doesn't overwrite the mscp block until the running    */
	/* command is finished.  mscp_free is set in the interrupt handler. */
	/* I'm not sure if the upper level driver will send another command */
	/* with a command pending; this is just insurance.                  */
	while (1) {
		cli();
		if (mscp_free) {
			mscp_free = FALSE;
			sti();
			break;
		}
		sti();
	}
    mscp.opcode = OP_SCSI;
    mscp.xdir = DTD_SCSI;
    mscp.dcn = FALSE;
    /* Tape drives don't work properly if the cache is used.  The SCSI
       READ command for a tape doesn't have a block offset, and the adapter
       incorrectly assumes that all reads from the tape read the same
       blocks.  Results will depend on read buffer size and other disk
       activity. 

       ???  Which other device types should never use the cache?   */
    mscp.ca = scsi_devices[SCpnt->index].type != TYPE_TAPE;
    mscp.target_id = SCpnt->target;
    mscp.ch_no = 0;
    mscp.lun = SCpnt->lun;
	if (SCpnt->use_sg) {
		/* Set scatter/gather flag in SCSI command packet */
		mscp.sg = TRUE;
		build_sg_list(SCpnt);
	}
	else {
		/* Unset scatter/gather flag in SCSI command packet */
		mscp.sg = FALSE;
		mscp.transfer_data = *(Longword *)&SCpnt->request_buffer;
		mscp.transfer_data_length = *(Longword *)&SCpnt->request_bufflen;
		SCpnt->host_scribble = NULL;
	}
    memset(&mscp.command_link, 0, sizeof(mscp.command_link));	/*???*/
    mscp.scsi_command_link_id = 0;	/*???*/
    mscp.length_of_sense_byte = 0;	/*???*/
    mscp.length_of_scsi_cdbs = COMMAND_SIZE(*(unsigned char *)SCpnt->cmnd);
    memcpy(mscp.scsi_cdbs, SCpnt->cmnd, mscp.length_of_scsi_cdbs);
    mscp.adapter_status = 0;
    mscp.target_status = 0;
    memset(&mscp.sense_data, 0, sizeof(mscp.sense_data));	/*???*/

    /* Find free OGM slot (OGMINT bit is 0) */
    do
	in_byte = inb_p(LCL_DOORBELL_INTR(PORT_ADDRESS));
    while (!aborted && (in_byte & 1));
    if (aborted) {
#if (ULTRASTOR_DEBUG & (UD_COMMAND | UD_ABORT))
	printk("US14F: queuecommand: aborted\n");
#endif
	/* ??? is this right? */
	return (aborted << 16);
    }

    /* Store pointer in OGM address bytes */
    outb_p(BYTE(&mscp, 0), OGM_DATA_PTR(PORT_ADDRESS + 0));
    outb_p(BYTE(&mscp, 1), OGM_DATA_PTR(PORT_ADDRESS + 1));
    outb_p(BYTE(&mscp, 2), OGM_DATA_PTR(PORT_ADDRESS + 2));
    outb_p(BYTE(&mscp, 3), OGM_DATA_PTR(PORT_ADDRESS + 3));

    /* Issue OGM interrupt */
    outb_p(0x1, LCL_DOORBELL_INTR(PORT_ADDRESS));

    ultrastor_done = done;
    SCint = SCpnt;

#if (ULTRASTOR_DEBUG & UD_COMMAND)
    printk("US14F: queuecommand: returning\n");
#endif

    return 0;
}

int ultrastor_abort(Scsi_Cmnd *SCpnt, int code)
{
#if (ULTRASTOR_DEBUG & UD_ABORT)
    printk("US14F: abort: called\n");
#endif

    aborted = (code ? code : DID_ABORT);

	/* Free DMA buffer used for scatter/gather list */
	if (SCpnt->host_scribble)
		scsi_free(SCpnt->host_scribble, 512);

	/* Free up mscp block for next command */
	mscp_free = TRUE;

#if (ULTRASTOR_DEBUG & UD_ABORT)
    printk("US14F: abort: returning\n");
#endif

    return 0;
}

int ultrastor_reset(void)
{
#if 0
    unsigned char in_byte;
#endif

#if (ULTRASTOR_DEBUG & UD_RESET)
    printk("US14F: reset: called\n");
#endif

	/* ??? SCSI bus reset causes problems on some systems. */
#if 0
    /* Issue SCSI BUS reset */
    outb_p(0x20, LCL_DOORBELL_INTR(PORT_ADDRESS));

    /* Wait for completion... */
    do
	in_byte = inb_p(LCL_DOORBELL_INTR(PORT_ADDRESS));
    while (in_byte & 0x20);

    aborted = DID_RESET;
#endif

#if (ULTRASTOR_DEBUG & UD_RESET)
    printk("US14F: reset: returning\n");
#endif
    return 0;
}

int ultrastor_biosparam(int size, int dev, int *ip)
{
    unsigned int s = config.heads * config.sectors;

    ip[0] = config.heads;
    ip[1] = config.sectors;
    ip[2] = (size + (s - 1)) / s;
/*    if (ip[2] > 1024)
	ip[2] = 1024; */
    return 0;
}

static void ultrastor_interrupt(int cpl)
{
#if (ULTRASTOR_DEBUG & UD_INTERRUPT)
    printk("US14F: interrupt: called: status = %08X\n",
	   (mscp.adapter_status << 16) | mscp.target_status);
#endif

    if (ultrastor_done == 0)
	panic("US14F: interrupt: unexpected interrupt");
    else {
	void (*done)(Scsi_Cmnd *);
	Scsi_Cmnd *SCtmp;

	/* Save ultrastor_done locally and zero before calling.  This is needed
	   as once we call done, we may get another command queued before this
	   interrupt service routine can return. */
	done = ultrastor_done;
	ultrastor_done = 0;
	SCtmp = SCint;

	/* Clean ICM slot (set ICMINT bit to 0) */
	outb_p(0x1, SYS_DOORBELL_INTR(PORT_ADDRESS));

	/* Let the higher levels know that we're done */
	/* ??? status is wrong here... */
	SCtmp->result = (mscp.adapter_status << 16) | mscp.target_status;

	/* Free temp space used for scatter-gather list */
	if (SCtmp->host_scribble)
		scsi_free(SCtmp->host_scribble, 512);

	/* Free up mscp block for next command */
	mscp_free = TRUE;

	done(SCtmp);
    }

#if (ULTRASTOR_DEBUG & UD_INTERRUPT)
    printk("US14F: interrupt: returning\n");
#endif
}
