aboutsummaryrefslogblamecommitdiffstats
path: root/drivers/scsi/psi240i.c
blob: ac0419e2714ae2df0edee98aa45a2915b84c08da (plain) (tree)
























































































                                                                                                      
                                 


                                     
                                


























































































































































                                                                                                                                                   



                                                                   
                                               
         





                                                                               











































































































                                                                                                                                     
                                                         




                                                 
                                 














                                                                                 

                                                                 
         







                                                                 













































































































































                                                                                                                                            
                                                           























































































































                                                                                                                                  
                                                              








                                  
                                                    












                                                                  
/*+M*************************************************************************
 * Perceptive Solutions, Inc. PSI-240I device driver proc support for Linux.
 *
 * Copyright (c) 1997 Perceptive Solutions, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 *	File Name:		psi240i.c
 *
 *	Description:	SCSI driver for the PSI240I EIDE interface card.
 *
 *-M*************************************************************************/

#include <linux/module.h>

#include <linux/blkdev.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/ioport.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
#include <linux/spinlock.h>
#include <linux/stat.h>

#include <asm/dma.h>
#include <asm/system.h>
#include <asm/io.h>
#include "scsi.h"
#include <scsi/scsi_host.h>

#include "psi240i.h"
#include "psi_chip.h"

//#define DEBUG 1

#ifdef DEBUG
#define DEB(x) x
#else
#define DEB(x)
#endif

#define MAXBOARDS 6	/* Increase this and the sizes of the arrays below, if you need more. */

#define	PORT_DATA				0
#define	PORT_ERROR				1
#define	PORT_SECTOR_COUNT		2
#define	PORT_LBA_0				3
#define	PORT_LBA_8				4
#define	PORT_LBA_16				5
#define	PORT_LBA_24				6
#define	PORT_STAT_CMD			7
#define	PORT_SEL_FAIL			8
#define	PORT_IRQ_STATUS			9
#define	PORT_ADDRESS			10
#define	PORT_FAIL				11
#define	PORT_ALT_STAT		   	12

typedef struct
	{
	UCHAR		   	device;				// device code
	UCHAR			byte6;				// device select register image
	UCHAR			spigot;				// spigot number
	UCHAR			expectingIRQ;		// flag for expecting and interrupt
	USHORT			sectors;			// number of sectors per track
	USHORT			heads;				// number of heads
	USHORT			cylinders;			// number of cylinders for this device
	USHORT			spareword;			// placeholder
	ULONG			blocks;				// number of blocks on device
	}	OUR_DEVICE, *POUR_DEVICE;

typedef struct
	{
	USHORT		 ports[13];
	OUR_DEVICE	 device[8];
	struct scsi_cmnd *pSCmnd;
	IDE_STRUCT	 ide;
	ULONG		 startSector;
	USHORT		 sectorCount;
	struct scsi_cmnd *SCpnt;
	VOID		*buffer;
	USHORT		 expectingIRQ;
	}	ADAPTER240I, *PADAPTER240I;

#define HOSTDATA(host) ((PADAPTER240I)&host->hostdata)

static struct	Scsi_Host *PsiHost[6] = {NULL,};  /* One for each IRQ level (10-15) */
static			IDENTIFY_DATA	identifyData;
static			SETUP			ChipSetup;

static	USHORT	portAddr[6] = {CHIP_ADRS_0, CHIP_ADRS_1, CHIP_ADRS_2, CHIP_ADRS_3, CHIP_ADRS_4, CHIP_ADRS_5};

/****************************************************************
 *	Name:	WriteData	:LOCAL
 *
 *	Description:	Write data to device.
 *
 *	Parameters:		padapter - Pointer adapter data structure.
 *
 *	Returns:		TRUE if drive does not assert DRQ in time.
 *
 ****************************************************************/
static int WriteData (PADAPTER240I padapter)
	{
	ULONG	timer;
	USHORT *pports = padapter->ports;

	timer = jiffies + TIMEOUT_DRQ;								// calculate the timeout value
	do  {
		if ( inb_p (pports[PORT_STAT_CMD]) & IDE_STATUS_DRQ )
			{
			outsw (pports[PORT_DATA], padapter->buffer, (USHORT)padapter->ide.ide.ide[2] * 256);
			return 0;
			}
		}	while ( time_after(timer, jiffies) );									// test for timeout

	padapter->ide.ide.ides.cmd = 0;									// null out the command byte
	return 1;
	}
/****************************************************************
 *	Name:	IdeCmd	:LOCAL
 *
 *	Description:	Process a queued command from the SCSI manager.
 *
 *	Parameters:		padapter - Pointer adapter data structure.
 *
 *	Returns:		Zero if no error or status register contents on error.
 *
 ****************************************************************/
static UCHAR IdeCmd (PADAPTER240I padapter)
	{
	ULONG	timer;
	USHORT *pports = padapter->ports;
	UCHAR	status;

	outb_p (padapter->ide.ide.ides.spigot, pports[PORT_SEL_FAIL]);	// select the spigot
	outb_p (padapter->ide.ide.ide[6], pports[PORT_LBA_24]);			// select the drive
	timer = jiffies + TIMEOUT_READY;							// calculate the timeout value
	do  {
		status = inb_p (padapter->ports[PORT_STAT_CMD]);
		if ( status & IDE_STATUS_DRDY )
			{
			outb_p (padapter->ide.ide.ide[2], pports[PORT_SECTOR_COUNT]);
			outb_p (padapter->ide.ide.ide[3], pports[PORT_LBA_0]);
			outb_p (padapter->ide.ide.ide[4], pports[PORT_LBA_8]);
			outb_p (padapter->ide.ide.ide[5], pports[PORT_LBA_16]);
			padapter->expectingIRQ = 1;
			outb_p (padapter->ide.ide.ide[7], pports[PORT_STAT_CMD]);

			if ( padapter->ide.ide.ides.cmd == IDE_CMD_WRITE_MULTIPLE )
				return (WriteData (padapter));

			return 0;
			}
		}	while ( time_after(timer, jiffies) );									// test for timeout

	padapter->ide.ide.ides.cmd = 0;									// null out the command byte
	return status;
	}
/****************************************************************
 *	Name:	SetupTransfer	:LOCAL
 *
 *	Description:	Setup a data transfer command.
 *
 *	Parameters:		padapter - Pointer adapter data structure.
 *					drive	 - Drive/head register upper nibble only.
 *
 *	Returns:		TRUE if no data to transfer.
 *
 ****************************************************************/
static int SetupTransfer (PADAPTER240I padapter, UCHAR drive)
	{
	if ( padapter->sectorCount )
		{
		*(ULONG *)padapter->ide.ide.ides.lba = padapter->startSector;
		padapter->ide.ide.ide[6] |= drive;
		padapter->ide.ide.ides.sectors = ( padapter->sectorCount > SECTORSXFER ) ? SECTORSXFER : padapter->sectorCount;
		padapter->sectorCount -= padapter->ide.ide.ides.sectors;	// bump the start and count for next xfer
		padapter->startSector += padapter->ide.ide.ides.sectors;
		return 0;
		}
	else
		{
		padapter->ide.ide.ides.cmd = 0;								// null out the command byte
		padapter->SCpnt = NULL;
		return 1;
		}
	}
/****************************************************************
 *	Name:	DecodeError	:LOCAL
 *
 *	Description:	Decode and process device errors.
 *
 *	Parameters:		pshost - Pointer to host data block.
 *					status - Status register code.
 *
 *	Returns:		The driver status code.
 *
 ****************************************************************/
static ULONG DecodeError (struct Scsi_Host *pshost, UCHAR status)
	{
	PADAPTER240I	padapter = HOSTDATA(pshost);
	UCHAR			error;

	padapter->expectingIRQ = 0;
	padapter->SCpnt = NULL;
	if ( status & IDE_STATUS_WRITE_FAULT )
		{
		return DID_PARITY << 16;
		}
	if ( status & IDE_STATUS_BUSY )
		return DID_BUS_BUSY << 16;

	error = inb_p (padapter->ports[PORT_ERROR]);
	DEB(printk ("\npsi240i error register: %x", error));
	switch ( error )
		{
		case IDE_ERROR_AMNF:
		case IDE_ERROR_TKONF:
		case IDE_ERROR_ABRT:
		case IDE_ERROR_IDFN:
		case IDE_ERROR_UNC:
		case IDE_ERROR_BBK:
		default:
			return DID_ERROR << 16;
		}
	return DID_ERROR << 16;
	}
/****************************************************************
 *	Name:	Irq_Handler	:LOCAL
 *
 *	Description:	Interrupt handler.
 *
 *	Parameters:		irq		- Hardware IRQ number.
 *					dev_id	-
 *
 *	Returns:		TRUE if drive is not ready in time.
 *
 ****************************************************************/
static void Irq_Handler (int irq, void *dev_id)
	{
	struct Scsi_Host *shost;	// Pointer to host data block
	PADAPTER240I padapter;		// Pointer to adapter control structure
	USHORT *pports;			// I/O port array
	struct scsi_cmnd *SCpnt;
	UCHAR status;
	int z;

	DEB(printk ("\npsi240i received interrupt\n"));

	shost = PsiHost[irq - 10];
	if ( !shost )
		panic ("Splunge!");

	padapter = HOSTDATA(shost);
	pports = padapter->ports;
	SCpnt = padapter->SCpnt;

	if ( !padapter->expectingIRQ )
		{
		DEB(printk ("\npsi240i Unsolicited interrupt\n"));
		return;
		}
	padapter->expectingIRQ = 0;

	status = inb_p (padapter->ports[PORT_STAT_CMD]);			// read the device status
	if ( status & (IDE_STATUS_ERROR | IDE_STATUS_WRITE_FAULT) )
		goto irqerror;

	DEB(printk ("\npsi240i processing interrupt"));
	switch ( padapter->ide.ide.ides.cmd )							// decide how to handle the interrupt
		{
		case IDE_CMD_READ_MULTIPLE:
			if ( status & IDE_STATUS_DRQ )
				{
				insw (pports[PORT_DATA], padapter->buffer, (USHORT)padapter->ide.ide.ides.sectors * 256);
				padapter->buffer += padapter->ide.ide.ides.sectors * 512;
				if ( SetupTransfer (padapter, padapter->ide.ide.ide[6] & 0xF0) )
					{
					SCpnt->result = DID_OK << 16;
					padapter->SCpnt = NULL;
					SCpnt->scsi_done (SCpnt);
					return;
					}
				if ( !(status = IdeCmd (padapter)) )
					return;
				}
			break;

		case IDE_CMD_WRITE_MULTIPLE:
			padapter->buffer += padapter->ide.ide.ides.sectors * 512;
			if ( SetupTransfer (padapter, padapter->ide.ide.ide[6] & 0xF0) )
				{
				SCpnt->result = DID_OK << 16;
				padapter->SCpnt = NULL;
				SCpnt->scsi_done (SCpnt);
				return;
				}
			if ( !(status = IdeCmd (padapter)) )
				return;
			break;

		case IDE_COMMAND_IDENTIFY:
			{
			PINQUIRYDATA	pinquiryData  = SCpnt->request_buffer;

			if ( status & IDE_STATUS_DRQ )
				{
				insw (pports[PORT_DATA], &identifyData, sizeof (identifyData) >> 1);

				memset (pinquiryData, 0, SCpnt->request_bufflen);		// Zero INQUIRY data structure.
				pinquiryData->DeviceType = 0;
				pinquiryData->Versions = 2;
				pinquiryData->AdditionalLength = 35 - 4;

				// Fill in vendor identification fields.
				for ( z = 0;  z < 20;  z += 2 )
					{
					pinquiryData->VendorId[z]	  = ((UCHAR *)identifyData.ModelNumber)[z + 1];
					pinquiryData->VendorId[z + 1] = ((UCHAR *)identifyData.ModelNumber)[z];
					}

				// Initialize unused portion of product id.
				for ( z = 0;  z < 4;  z++ )
					pinquiryData->ProductId[12 + z] = ' ';

				// Move firmware revision from IDENTIFY data to
				// product revision in INQUIRY data.
				for ( z = 0;  z < 4;  z += 2 )
					{
					pinquiryData->ProductRevisionLevel[z]	 = ((UCHAR *)identifyData.FirmwareRevision)[z + 1];
					pinquiryData->ProductRevisionLevel[z + 1] = ((UCHAR *)identifyData.FirmwareRevision)[z];
					}

				SCpnt->result = DID_OK << 16;
				padapter->SCpnt = NULL;
				SCpnt->scsi_done (SCpnt);
				return;
				}
			break;
			}

		default:
			SCpnt->result = DID_OK << 16;
			padapter->SCpnt = NULL;
			SCpnt->scsi_done (SCpnt);
			return;
		}

irqerror:;
	DEB(printk ("\npsi240i error  Device Status: %X\n", status));
	SCpnt->result = DecodeError (shost, status);
	SCpnt->scsi_done (SCpnt);
	}

static irqreturn_t do_Irq_Handler (int irq, void *dev_id)
{
	unsigned long flags;
	struct Scsi_Host *dev = dev_id;
	
	spin_lock_irqsave(dev->host_lock, flags);
	Irq_Handler(irq, dev_id);
	spin_unlock_irqrestore(dev->host_lock, flags);
	return IRQ_HANDLED;
}

/****************************************************************
 *	Name:	Psi240i_QueueCommand
 *
 *	Description:	Process a queued command from the SCSI manager.
 *
 *	Parameters:		SCpnt - Pointer to SCSI command structure.
 *					done  - Pointer to done function to call.
 *
 *	Returns:		Status code.
 *
 ****************************************************************/
static int Psi240i_QueueCommand(struct scsi_cmnd *SCpnt,
				void (*done)(struct scsi_cmnd *))
	{
	UCHAR *cdb = (UCHAR *)SCpnt->cmnd;
	// Pointer to SCSI CDB
	PADAPTER240I padapter = HOSTDATA (SCpnt->device->host);
	// Pointer to adapter control structure
	POUR_DEVICE pdev = &padapter->device [SCpnt->device->id];
	// Pointer to device information
	UCHAR rc;
	// command return code

	SCpnt->scsi_done = done;
	padapter->ide.ide.ides.spigot = pdev->spigot;
	padapter->buffer = SCpnt->request_buffer;
	if (done)
		{
		if ( !pdev->device )
			{
			SCpnt->result = DID_BAD_TARGET << 16;
			done (SCpnt);
			return 0;
			}
		}
	else
		{
		printk("psi240i_queuecommand: %02X: done can't be NULL\n", *cdb);
		return 0;
		}

	switch ( *cdb )
		{
		case SCSIOP_INQUIRY:   					// inquiry CDB
			{
			padapter->ide.ide.ide[6] = pdev->byte6;
			padapter->ide.ide.ides.cmd = IDE_COMMAND_IDENTIFY;
			break;
			}

		case SCSIOP_TEST_UNIT_READY:			// test unit ready CDB
			SCpnt->result = DID_OK << 16;
			done (SCpnt);
			return 0;

		case SCSIOP_READ_CAPACITY:			  	// read capctiy CDB
			{
			PREAD_CAPACITY_DATA	pdata = (PREAD_CAPACITY_DATA)SCpnt->request_buffer;

			pdata->blksiz = 0x20000;
			XANY2SCSI ((UCHAR *)&pdata->blks, pdev->blocks);
			SCpnt->result = DID_OK << 16;
			done (SCpnt);
			return 0;
			}

		case SCSIOP_VERIFY:						// verify CDB
			*(ULONG *)padapter->ide.ide.ides.lba = XSCSI2LONG (&cdb[2]);
			padapter->ide.ide.ide[6] |= pdev->byte6;
			padapter->ide.ide.ide[2] = (UCHAR)((USHORT)cdb[8] | ((USHORT)cdb[7] << 8));
			padapter->ide.ide.ides.cmd = IDE_COMMAND_VERIFY;
			break;

		case SCSIOP_READ:						// read10 CDB
			padapter->startSector = XSCSI2LONG (&cdb[2]);
			padapter->sectorCount = (USHORT)cdb[8] | ((USHORT)cdb[7] << 8);
			SetupTransfer (padapter, pdev->byte6);
			padapter->ide.ide.ides.cmd = IDE_CMD_READ_MULTIPLE;
			break;

		case SCSIOP_READ6:						// read6  CDB
			padapter->startSector = SCSI2LONG (&cdb[1]);
			padapter->sectorCount = cdb[4];
			SetupTransfer (padapter, pdev->byte6);
			padapter->ide.ide.ides.cmd = IDE_CMD_READ_MULTIPLE;
			break;

		case SCSIOP_WRITE:						// write10 CDB
			padapter->startSector = XSCSI2LONG (&cdb[2]);
			padapter->sectorCount = (USHORT)cdb[8] | ((USHORT)cdb[7] << 8);
			SetupTransfer (padapter, pdev->byte6);
			padapter->ide.ide.ides.cmd = IDE_CMD_WRITE_MULTIPLE;
			break;
		case SCSIOP_WRITE6:						// write6  CDB
			padapter->startSector = SCSI2LONG (&cdb[1]);
			padapter->sectorCount = cdb[4];
			SetupTransfer (padapter, pdev->byte6);
			padapter->ide.ide.ides.cmd = IDE_CMD_WRITE_MULTIPLE;
			break;

		default:
			DEB (printk ("psi240i_queuecommand: Unsupported command %02X\n", *cdb));
			SCpnt->result = DID_ERROR << 16;
			done (SCpnt);
			return 0;
		}

	padapter->SCpnt = SCpnt;  									// Save this command data

	rc = IdeCmd (padapter);
	if ( rc )
		{
		padapter->expectingIRQ = 0;
		DEB (printk ("psi240i_queuecommand: %02X, %02X: Device failed to respond for command\n", *cdb, padapter->ide.ide.ides.cmd));
		SCpnt->result = DID_ERROR << 16;
		done (SCpnt);
		return 0;
		}
	DEB (printk("psi240i_queuecommand: %02X, %02X now waiting for interrupt ", *cdb, padapter->ide.ide.ides.cmd));
	return 0;
	}

/***************************************************************************
 *	Name:			ReadChipMemory
 *
 *	Description:	Read information from controller memory.
 *
 *	Parameters:		psetup	- Pointer to memory image of setup information.
 *					base	- base address of memory.
 *					length	- lenght of data space in bytes.
 *					port	- I/O address of data port.
 *
 *	Returns:		Nothing.
 *
 **************************************************************************/
static void ReadChipMemory (void *pdata, USHORT base, USHORT length, USHORT port)
	{
	USHORT	z, zz;
	UCHAR	*pd = (UCHAR *)pdata;
	outb_p (SEL_NONE, port + REG_SEL_FAIL);				// setup data port
	zz = 0;
	while ( zz < length )
		{
		outw_p (base, port + REG_ADDRESS);				// setup address

		for ( z = 0;  z < 8;  z++ )
			{
			if ( (zz + z) < length )
			*pd++ = inb_p (port + z);	// read data byte
			}
		zz += 8;
		base += 8;
		}
	}
/****************************************************************
 *	Name:	Psi240i_Detect
 *
 *	Description:	Detect and initialize our boards.
 *
 *	Parameters:		tpnt - Pointer to SCSI host template structure.
 *
 *	Returns:		Number of adapters found.
 *
 ****************************************************************/
static int Psi240i_Detect (struct scsi_host_template *tpnt)
	{
	int					board;
	int					count = 0;
	int					unit;
	int					z;
	USHORT				port, port_range = 16;
	CHIP_CONFIG_N		chipConfig;
	CHIP_DEVICE_N		chipDevice[8];
	struct Scsi_Host   *pshost;

	for ( board = 0;  board < MAXBOARDS;  board++ )					// scan for I/O ports
		{
		pshost = NULL;
		port = portAddr[board];								// get base address to test
		if ( !request_region (port, port_range, "psi240i") )
			continue;
		if ( inb_p (port + REG_FAIL) != CHIP_ID )			// do the first test for likley hood that it is us
			goto host_init_failure;
		outb_p (SEL_NONE, port + REG_SEL_FAIL);				// setup EEPROM/RAM access
		outw (0, port + REG_ADDRESS);						// setup EEPROM address zero
		if ( inb_p (port) != 0x55 )							// test 1st byte
			goto host_init_failure;									//   nope
		if ( inb_p (port + 1) != 0xAA )						// test 2nd byte
			goto host_init_failure;								//   nope

		// at this point our board is found and can be accessed.  Now we need to initialize
		// our informatation and register with the kernel.


		ReadChipMemory (&chipConfig, CHIP_CONFIG, sizeof (chipConfig), port);
		ReadChipMemory (&chipDevice, CHIP_DEVICE, sizeof (chipDevice), port);
		ReadChipMemory (&ChipSetup, CHIP_EEPROM_DATA, sizeof (ChipSetup), port);

		if ( !chipConfig.numDrives )						// if no devices on this board
			goto host_init_failure;

		pshost = scsi_register (tpnt, sizeof(ADAPTER240I));
		if(pshost == NULL)
			goto host_init_failure;	

		PsiHost[chipConfig.irq - 10] = pshost;
		pshost->unique_id = port;
		pshost->io_port = port;
		pshost->n_io_port = 16;  /* Number of bytes of I/O space used */
		pshost->irq = chipConfig.irq;

		for ( z = 0;  z < 11;  z++ )						// build regester address array
			HOSTDATA(pshost)->ports[z] = port + z;
		HOSTDATA(pshost)->ports[11] = port + REG_FAIL;
		HOSTDATA(pshost)->ports[12] = port + REG_ALT_STAT;
		DEB (printk ("\nPorts ="));
		DEB (for (z=0;z<13;z++) printk(" %#04X",HOSTDATA(pshost)->ports[z]););

		for ( z = 0;  z < chipConfig.numDrives;  ++z )
			{
			unit = chipDevice[z].channel & 0x0F;
			HOSTDATA(pshost)->device[unit].device	 = ChipSetup.setupDevice[unit].device;
			HOSTDATA(pshost)->device[unit].byte6	 = (UCHAR)(((unit & 1) << 4) | 0xE0);
			HOSTDATA(pshost)->device[unit].spigot	 = (UCHAR)(1 << (unit >> 1));
			HOSTDATA(pshost)->device[unit].sectors	 = ChipSetup.setupDevice[unit].sectors;
			HOSTDATA(pshost)->device[unit].heads	 = ChipSetup.setupDevice[unit].heads;
			HOSTDATA(pshost)->device[unit].cylinders = ChipSetup.setupDevice[unit].cylinders;
			HOSTDATA(pshost)->device[unit].blocks	 = ChipSetup.setupDevice[unit].blocks;
			DEB (printk ("\nHOSTDATA->device    = %X", HOSTDATA(pshost)->device[unit].device));
			DEB (printk ("\n          byte6     = %X", HOSTDATA(pshost)->device[unit].byte6));
			DEB (printk ("\n          spigot    = %X", HOSTDATA(pshost)->device[unit].spigot));
			DEB (printk ("\n          sectors   = %X", HOSTDATA(pshost)->device[unit].sectors));
			DEB (printk ("\n          heads     = %X", HOSTDATA(pshost)->device[unit].heads));
			DEB (printk ("\n          cylinders = %X", HOSTDATA(pshost)->device[unit].cylinders));
			DEB (printk ("\n          blocks    = %lX", HOSTDATA(pshost)->device[unit].blocks));
			}

		if ( request_irq (chipConfig.irq, do_Irq_Handler, 0, "psi240i", pshost) == 0 ) 
			{
			printk("\nPSI-240I EIDE CONTROLLER: at I/O = %x  IRQ = %d\n", port, chipConfig.irq);
		        printk("(C) 1997 Perceptive Solutions, Inc. All rights reserved\n\n");
		        count++;
		        continue;
			}

		printk ("Unable to allocate IRQ for PSI-240I controller.\n");
           
host_init_failure:
		
		release_region (port, port_range);
		if (pshost)
			scsi_unregister (pshost);

		}
	return count;
	}

static int Psi240i_Release(struct Scsi_Host *shost)
{
	if (shost->irq)
		free_irq(shost->irq, NULL);
	if (shost->io_port && shost->n_io_port)
		release_region(shost->io_port, shost->n_io_port);
	scsi_unregister(shost);
	return 0;
}

/****************************************************************
 *	Name:	Psi240i_BiosParam
 *
 *	Description:	Process the biosparam request from the SCSI manager to
 *					return C/H/S data.
 *
 *	Parameters:		disk - Pointer to SCSI disk structure.
 *					dev	 - Major/minor number from kernel.
 *					geom - Pointer to integer array to place geometry data.
 *
 *	Returns:		zero.
 *
 ****************************************************************/
static int Psi240i_BiosParam (struct scsi_device *sdev, struct block_device *dev,
		sector_t capacity, int geom[])
	{
	POUR_DEVICE	pdev;

	pdev = &(HOSTDATA(sdev->host)->device[sdev_id(sdev)]);

	geom[0] = pdev->heads;
	geom[1] = pdev->sectors;
	geom[2] = pdev->cylinders;
	return 0;
	}

MODULE_LICENSE("GPL");

static struct scsi_host_template driver_template = {
	.proc_name		= "psi240i", 
	.name			= "PSI-240I EIDE Disk Controller",
	.detect			= Psi240i_Detect,
	.release		= Psi240i_Release,
	.queuecommand		= Psi240i_QueueCommand,
	.bios_param	  	= Psi240i_BiosParam,
	.can_queue	  	= 1,
	.this_id	  	= -1,
	.sg_tablesize	  	= SG_NONE,
	.cmd_per_lun	  	= 1, 
	.use_clustering		= DISABLE_CLUSTERING,
};
#include "scsi_module.c"