/*-
 * Written by: David Jeffery
 * Copyright (c) 2002 Adaptec Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include <dev/ips/ips.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <machine/clock.h>

static d_open_t ips_open;
static d_close_t ips_close;
static d_ioctl_t ips_ioctl;

#define IPS_CDEV_MAJOR 175
static struct cdevsw ips_cdevsw = {
	.d_open = ips_open,
	.d_close = ips_close,
	.d_ioctl = ips_ioctl,
	.d_name = "ips",
	.d_maj = IPS_CDEV_MAJOR,
};


static int ips_open(dev_t dev, int flags, int fmt, struct thread *td)
{
	ips_softc_t *sc = dev->si_drv1;
	sc->state |= IPS_DEV_OPEN;
        return 0;
}

static int ips_close(dev_t dev, int flags, int fmt, struct thread *td)
{
	ips_softc_t *sc = dev->si_drv1;
	sc->state &= ~IPS_DEV_OPEN;

        return 0;
}

static int ips_ioctl(dev_t dev, u_long command, caddr_t addr, int32_t flags, struct thread *td)
{
	ips_softc_t *sc;

	sc = dev->si_drv1;
	return ips_ioctl_request(sc, command, addr, flags);
}

static void ips_cmd_dmaload(void *cmdptr, bus_dma_segment_t *segments,int segnum, int error)
{
	ips_command_t *command = cmdptr;
	PRINTF(10, "ips: in ips_cmd_dmaload\n");
	if(!error)
		command->command_phys_addr = segments[0].ds_addr;

}

/* is locking needed? what locking guarentees are there on removal? */
static __inline__ int ips_cmdqueue_free(ips_softc_t *sc)
{
	int i, error = -1;
	intrmask_t mask = splbio();
	if(!sc->used_commands){
		for(i = 0; i < sc->max_cmds; i++){
			if(!(sc->commandarray[i].command_phys_addr))
				continue;
			bus_dmamap_unload(sc->command_dmatag, 
					  sc->commandarray[i].command_dmamap);
			bus_dmamem_free(sc->command_dmatag, 
					sc->commandarray[i].command_buffer,
					sc->commandarray[i].command_dmamap);
		}
		error = 0;
		sc->state |= IPS_OFFLINE;
	}
	splx(mask);
	return error;
}

/* places all ips command structs on the free command queue.  No locking as if someone else tries
 * to access this during init, we have bigger problems */
static __inline__ int ips_cmdqueue_init(ips_softc_t *sc)
{
	int i;
	ips_command_t *command;
	SLIST_INIT(&sc->free_cmd_list);
	STAILQ_INIT(&sc->cmd_wait_list);
	for(i = 0; i < sc->max_cmds; i++){
		sc->commandarray[i].id = i;
		sc->commandarray[i].sc = sc;
		SLIST_INSERT_HEAD(&sc->free_cmd_list, &sc->commandarray[i], 
				  next);	
	}
	for(i = 0; i < sc->max_cmds; i++){
		command = &sc->commandarray[i];
		if(bus_dmamem_alloc(sc->command_dmatag,&command->command_buffer,
		    BUS_DMA_NOWAIT, &command->command_dmamap))
			goto error;
		bus_dmamap_load(sc->command_dmatag, command->command_dmamap, 
				command->command_buffer,IPS_COMMAND_LEN, 
				ips_cmd_dmaload, command, BUS_DMA_NOWAIT);
		if(!command->command_phys_addr){
			bus_dmamem_free(sc->command_dmatag, 
			    command->command_buffer, command->command_dmamap);
			goto error;
		}
	}
	sc->state &= ~IPS_OFFLINE;
	return 0;
error:
		ips_cmdqueue_free(sc);
		return ENOMEM;
}

static int ips_add_waiting_command(ips_softc_t *sc, int (*callback)(ips_command_t *), void *data, unsigned long flags)
{
	intrmask_t mask;
	ips_command_t *command;
	ips_wait_list_t *waiter;
	unsigned long memflags = 0;
	if(IPS_NOWAIT_FLAG & flags)
		memflags = M_NOWAIT;
	waiter = malloc(sizeof(ips_wait_list_t), M_DEVBUF, memflags);
	if(!waiter)
		return ENOMEM;
	mask = splbio();
	if(sc->state & IPS_OFFLINE){
		splx(mask);
		return EIO;
	}
	command = SLIST_FIRST(&sc->free_cmd_list);
	if(command && !(sc->state & IPS_TIMEOUT)){
		SLIST_REMOVE_HEAD(&sc->free_cmd_list, next);
		(sc->used_commands)++;
		splx(mask);
		clear_ips_command(command);
		bzero(command->command_buffer, IPS_COMMAND_LEN);
		free(waiter, M_DEVBUF);
		command->arg = data;
		return callback(command);
	}
	DEVICE_PRINTF(1, sc->dev, "adding command to the wait queue\n"); 
	waiter->callback = callback; 
	waiter->data = data;
	STAILQ_INSERT_TAIL(&sc->cmd_wait_list, waiter, next);
	splx(mask);
	return 0;
}

static void ips_run_waiting_command(ips_softc_t *sc)
{
	ips_wait_list_t *waiter;
	ips_command_t	*command;
	int (*callback)(ips_command_t*);
	intrmask_t mask;

	mask = splbio();
	waiter = STAILQ_FIRST(&sc->cmd_wait_list);
	command = SLIST_FIRST(&sc->free_cmd_list);
	if(!waiter || !command){
		splx(mask);
		return;
	}
	DEVICE_PRINTF(1, sc->dev, "removing command from wait queue\n");
	SLIST_REMOVE_HEAD(&sc->free_cmd_list, next);
	STAILQ_REMOVE_HEAD(&sc->cmd_wait_list, next);
	(sc->used_commands)++;
	splx(mask);
	clear_ips_command(command);
	bzero(command->command_buffer, IPS_COMMAND_LEN);
	command->arg = waiter->data;
	callback = waiter->callback;
	free(waiter, M_DEVBUF);
	callback(command);
	return;	
}
/* returns a free command struct if one is available. 
 * It also blanks out anything that may be a wild pointer/value.
 * Also, command buffers are not freed.  They are
 * small so they are saved and kept dmamapped and loaded.
 */
int ips_get_free_cmd(ips_softc_t *sc, int (*callback)(ips_command_t *), void *data, unsigned long flags)
{
	intrmask_t mask;
	ips_command_t *command;
	mask = splbio();

	if(sc->state & IPS_OFFLINE){
		splx(mask);
		return EIO;
	}
	command = SLIST_FIRST(&sc->free_cmd_list);
	if(!command || (sc->state & IPS_TIMEOUT)){
		splx(mask);
		if(flags & IPS_NOWAIT_FLAG)
			return EAGAIN;
		return ips_add_waiting_command(sc, callback, data, flags);
	}
	SLIST_REMOVE_HEAD(&sc->free_cmd_list, next);
	(sc->used_commands)++;
	splx(mask);
	clear_ips_command(command);
	bzero(command->command_buffer, IPS_COMMAND_LEN);
	command->arg = data;
	return callback(command);
}

/* adds a command back to the free command queue */
void ips_insert_free_cmd(ips_softc_t *sc, ips_command_t *command)
{
	intrmask_t mask;
	mask = splbio();
	SLIST_INSERT_HEAD(&sc->free_cmd_list, command, next);
	(sc->used_commands)--;
	splx(mask);
	if(!(sc->state & IPS_TIMEOUT))
		ips_run_waiting_command(sc);
}

static int ips_diskdev_init(ips_softc_t *sc)
{
	int i;
	for(i=0; i < IPS_MAX_NUM_DRIVES; i++){
		if(sc->drives[i].state & IPS_LD_OKAY){
			sc->diskdev[i] = device_add_child(sc->dev, NULL, -1);
			device_set_ivars(sc->diskdev[i],(void *)(uintptr_t) i);
		}
	}
	if(bus_generic_attach(sc->dev)){
		device_printf(sc->dev, "Attaching bus failed\n");
	}
	return 0;
}

static int ips_diskdev_free(ips_softc_t *sc)
{
	int i;
	int error = 0;
	for(i = 0; i < IPS_MAX_NUM_DRIVES; i++){
		if(sc->diskdev[i])
			error = device_delete_child(sc->dev, sc->diskdev[i]);
			if(error)
				return error;
	}
	bus_generic_detach(sc->dev);
	return 0;
}

/* ips_timeout is periodically called to make sure no commands sent
 * to the card have become stuck.  If it finds a stuck command, it
 * sets a flag so the driver won't start any more commands and then
 * is periodically called to see if all outstanding commands have
 * either finished or timed out.  Once timed out, an attempt to
 * reinitialize the card is made.  If that fails, the driver gives
 * up and declares the card dead. */
static void ips_timeout(void *arg)
{
	intrmask_t mask;
	ips_softc_t *sc = arg;
	int i, state = 0;
	ips_command_t *command;
	command = &sc->commandarray[0];
	mask = splbio();
	for(i = 0; i < sc->max_cmds; i++){
		if(!command[i].timeout){
			continue;
		}
		command[i].timeout--;
		if(!command[i].timeout){
			if(!(sc->state & IPS_TIMEOUT)){
				sc->state |= IPS_TIMEOUT;
				device_printf(sc->dev, "WARNING: command timeout. Adapter is in toaster mode, resetting to known state\n");
			}
			command[i].status.value = IPS_ERROR_STATUS;
			command[i].callback(&command[i]);
			/* hmm, this should be enough cleanup */
		} else
			state = 1;
	}
	if(!state && (sc->state & IPS_TIMEOUT)){
		if(sc->ips_adapter_reinit(sc, 1)){
			device_printf(sc->dev, "AIEE! adapter reset failed, giving up and going home! Have a nice day.\n");
			sc->state |= IPS_OFFLINE;
			sc->state &= ~IPS_TIMEOUT;
			/* Grr, I hate this solution. I run waiting commands
			   one at a time and error them out just before they 
			   would go to the card. This sucks. */
		} else
			sc->state &= ~IPS_TIMEOUT;
		ips_run_waiting_command(sc);
	}
	if (sc->state != IPS_OFFLINE)
		sc->timer = timeout(ips_timeout, sc, 10*hz);
	splx(mask);
}

/* check card and initialize it */
int ips_adapter_init(ips_softc_t *sc)
{
        int i;
        DEVICE_PRINTF(1,sc->dev, "initializing\n");
        if (bus_dma_tag_create(	/* parent    */	sc->adapter_dmatag,
				/* alignemnt */	1,
				/* boundary  */	0,
				/* lowaddr   */	BUS_SPACE_MAXADDR_32BIT,
				/* highaddr  */	BUS_SPACE_MAXADDR,
				/* filter    */	NULL,
				/* filterarg */	NULL,
				/* maxsize   */	IPS_COMMAND_LEN + 
						    IPS_MAX_SG_LEN,
				/* numsegs   */	1,
				/* maxsegsize*/	IPS_COMMAND_LEN + 
						    IPS_MAX_SG_LEN,
				/* flags     */	0,
				/* lockfunc  */ busdma_lock_mutex,
				/* lockarg   */ &Giant,
				&sc->command_dmatag) != 0) {
                device_printf(sc->dev, "can't alloc command dma tag\n");
		goto error;
        }
	if (bus_dma_tag_create(	/* parent    */	sc->adapter_dmatag,
				/* alignemnt */	1,
				/* boundary  */	0,
				/* lowaddr   */	BUS_SPACE_MAXADDR_32BIT,
				/* highaddr  */	BUS_SPACE_MAXADDR,
				/* filter    */	NULL,
				/* filterarg */	NULL,
				/* maxsize   */	IPS_MAX_IOBUF_SIZE,
				/* numsegs   */	IPS_MAX_SG_ELEMENTS,
				/* maxsegsize*/	IPS_MAX_IOBUF_SIZE,
				/* flags     */	0,
				/* lockfunc  */ busdma_lock_mutex,
				/* lockarg   */ &Giant,
				&sc->sg_dmatag) != 0) {
		device_printf(sc->dev, "can't alloc SG dma tag\n");
		goto error;
	}
	/* create one command buffer until we know how many commands this card
           can handle */
	sc->max_cmds = 1;
	ips_cmdqueue_init(sc);
	callout_handle_init(&sc->timer);

	if(sc->ips_adapter_reinit(sc, 0))
		goto error;

	mtx_init(&sc->cmd_mtx, "ips command mutex", NULL, MTX_DEF);
	if ((i = ips_get_adapter_info(sc)) != 0) {
		device_printf(sc->dev, "failed to get adapter configuration data from device (%d)\n", i);
		goto error;
	}
 	if ((i = ips_get_drive_info(sc)) != 0) {
		device_printf(sc->dev, "failed to get drive configuration data from device (%d)\n", i);
		goto error;
	}
	ips_update_nvram(sc); /* no error check as failure doesn't matter */

        ips_cmdqueue_free(sc);
	if(sc->adapter_info.max_concurrent_cmds)
        	sc->max_cmds = min(128, sc->adapter_info.max_concurrent_cmds);
	else
		sc->max_cmds = 32;
        if(ips_cmdqueue_init(sc)){
		device_printf(sc->dev, "failed to initialize command buffers\n");
		goto error;
	}
        sc->device_file = make_dev(&ips_cdevsw, device_get_unit(sc->dev), UID_ROOT, GID_OPERATOR,
                                        S_IRUSR | S_IWUSR, "ips%d", device_get_unit(sc->dev));
	sc->device_file->si_drv1 = sc;
	ips_diskdev_init(sc);
	sc->timer = timeout(ips_timeout, sc, 10*hz);
        return 0;

error:
	ips_adapter_free(sc);
	return ENXIO;
}

/* see if we should reinitialize the card and wait for it to timeout or complete initialization */
int ips_morpheus_reinit(ips_softc_t *sc, int force)
{
        u_int32_t tmp;
	int i;

	tmp = ips_read_4(sc, MORPHEUS_REG_OISR);
	if(!force && (ips_read_4(sc, MORPHEUS_REG_OMR0) >= IPS_POST1_OK) &&
	    (ips_read_4(sc, MORPHEUS_REG_OMR1) != 0xdeadbeef) && !tmp){
		ips_write_4(sc, MORPHEUS_REG_OIMR, 0);
		return 0;
	}
	ips_write_4(sc, MORPHEUS_REG_OIMR, 0xff);
	ips_read_4(sc, MORPHEUS_REG_OIMR);

	device_printf(sc->dev, "resetting adapter, this may take up to 5 minutes\n");
	ips_write_4(sc, MORPHEUS_REG_IDR, 0x80000000);
	DELAY(5000000);
	pci_read_config(sc->dev, 0, 4);

	tmp = ips_read_4(sc, MORPHEUS_REG_OISR);
	for(i = 0; i < 45 && !(tmp & MORPHEUS_BIT_POST1); i++){
		DELAY(1000000);
		DEVICE_PRINTF(2, sc->dev, "post1: %d\n", i);
		tmp = ips_read_4(sc, MORPHEUS_REG_OISR);
	}
	if(tmp & MORPHEUS_BIT_POST1)
		ips_write_4(sc, MORPHEUS_REG_OISR, MORPHEUS_BIT_POST1);

        if( i == 45 || ips_read_4(sc, MORPHEUS_REG_OMR0) < IPS_POST1_OK){
                device_printf(sc->dev,"Adapter error during initialization.\n");
		return 1;
        }
	for(i = 0; i < 240 && !(tmp & MORPHEUS_BIT_POST2); i++){
		DELAY(1000000);	
		DEVICE_PRINTF(2, sc->dev, "post2: %d\n", i);
		tmp = ips_read_4(sc, MORPHEUS_REG_OISR);
	}
	if(tmp & MORPHEUS_BIT_POST2)
		ips_write_4(sc, MORPHEUS_REG_OISR, MORPHEUS_BIT_POST2);

	if(i == 240 || !ips_read_4(sc, MORPHEUS_REG_OMR1)){
		device_printf(sc->dev, "adapter failed config check\n");
		return 1;
        }
	ips_write_4(sc, MORPHEUS_REG_OIMR, 0);
	if(force && ips_clear_adapter(sc)){
		device_printf(sc->dev, "adapter clear failed\n");
		return 1;
	}
	return 0;
}

/* clean up so we can unload the driver. */
int ips_adapter_free(ips_softc_t *sc)
{
	int error = 0;
	intrmask_t mask;
	if(sc->state & IPS_DEV_OPEN)
		return EBUSY;
	if((error = ips_diskdev_free(sc)))
		return error;
	if(ips_cmdqueue_free(sc)){
		device_printf(sc->dev,
		     "trying to exit when command queue is not empty!\n");
		return EBUSY;
	}
	DEVICE_PRINTF(1, sc->dev, "free\n");
	mask = splbio();
	untimeout(ips_timeout, sc, sc->timer);
	splx(mask);
	if (mtx_initialized(&sc->cmd_mtx))
		mtx_destroy(&sc->cmd_mtx);

	if(sc->sg_dmatag)
		bus_dma_tag_destroy(sc->sg_dmatag);
	if(sc->command_dmatag)
		bus_dma_tag_destroy(sc->command_dmatag);
	if(sc->device_file)
	        destroy_dev(sc->device_file);
        return 0;
}

void ips_morpheus_intr(void *void_sc)
{
        ips_softc_t *sc = (ips_softc_t *)void_sc;
	u_int32_t oisr, iisr;
	int cmdnumber;
	ips_cmd_status_t status;

	iisr =ips_read_4(sc, MORPHEUS_REG_IISR);
	oisr =ips_read_4(sc, MORPHEUS_REG_OISR);
        PRINTF(9,"interrupt registers in:%x out:%x\n",iisr, oisr);
	if(!(oisr & MORPHEUS_BIT_CMD_IRQ)){
		DEVICE_PRINTF(2,sc->dev, "got a non-command irq\n");
		return;	
	}
	while((status.value = ips_read_4(sc, MORPHEUS_REG_OQPR)) != 0xffffffff){
		cmdnumber = status.fields.command_id;
		sc->commandarray[cmdnumber].status.value = status.value;
		sc->commandarray[cmdnumber].timeout = 0;
		sc->commandarray[cmdnumber].callback(&(sc->commandarray[cmdnumber]));
		

		DEVICE_PRINTF(9,sc->dev, "got command %d\n", cmdnumber);
	}
        return;
}

void ips_issue_morpheus_cmd(ips_command_t *command)
{
	intrmask_t mask = splbio();
	/* hmmm, is there a cleaner way to do this? */
	if(command->sc->state & IPS_OFFLINE){
		splx(mask);
		command->status.value = IPS_ERROR_STATUS;
		command->callback(command);
		return;
	}
	command->timeout = 10;
	ips_write_4(command->sc, MORPHEUS_REG_IQPR, command->command_phys_addr);
	splx(mask);
}

static void ips_copperhead_queue_callback(void *queueptr, bus_dma_segment_t *segments,int segnum, int error)
{
	ips_copper_queue_t *queue = queueptr;
	if(error){
		return;
	}
	queue->base_phys_addr = segments[0].ds_addr;
}

static int ips_copperhead_queue_init(ips_softc_t *sc)
{
	int error;
	bus_dma_tag_t dmatag;
	bus_dmamap_t dmamap;
       	if (bus_dma_tag_create(	/* parent    */	sc->adapter_dmatag,
				/* alignemnt */	1,
				/* boundary  */	0,
				/* lowaddr   */	BUS_SPACE_MAXADDR_32BIT,
				/* highaddr  */	BUS_SPACE_MAXADDR,
				/* filter    */	NULL,
				/* filterarg */	NULL,
				/* maxsize   */	sizeof(ips_copper_queue_t),
				/* numsegs   */	1,
				/* maxsegsize*/	sizeof(ips_copper_queue_t),
				/* flags     */	0,
				/* lockfunc  */ busdma_lock_mutex,
				/* lockarg   */ &Giant,
				&dmatag) != 0) {
                device_printf(sc->dev, "can't alloc dma tag for statue queue\n");
		error = ENOMEM;
		goto exit;
        }
	if(bus_dmamem_alloc(dmatag, (void *)&(sc->copper_queue), 
	   		    BUS_DMA_NOWAIT, &dmamap)){
		error = ENOMEM;
		goto exit;
	}
	bzero(sc->copper_queue, sizeof(ips_copper_queue_t));
	sc->copper_queue->dmatag = dmatag;
	sc->copper_queue->dmamap = dmamap;
	sc->copper_queue->nextstatus = 1;
	bus_dmamap_load(dmatag, dmamap, 
			&(sc->copper_queue->status[0]), IPS_MAX_CMD_NUM * 4, 
			ips_copperhead_queue_callback, sc->copper_queue, 
			BUS_DMA_NOWAIT);
	if(sc->copper_queue->base_phys_addr == 0){
		error = ENOMEM;
		goto exit;
	}
	ips_write_4(sc, COPPER_REG_SQSR, sc->copper_queue->base_phys_addr);
	ips_write_4(sc, COPPER_REG_SQER, sc->copper_queue->base_phys_addr + 
		    IPS_MAX_CMD_NUM * 4);
	ips_write_4(sc, COPPER_REG_SQHR, sc->copper_queue->base_phys_addr + 4);
	ips_write_4(sc, COPPER_REG_SQTR, sc->copper_queue->base_phys_addr);

	
	return 0;
exit:
	bus_dmamem_free(dmatag, sc->copper_queue, dmamap);
	bus_dma_tag_destroy(dmatag);
	return error;
}

/* see if we should reinitialize the card and wait for it to timeout or complete initialization FIXME */
int ips_copperhead_reinit(ips_softc_t *sc, int force)
{
	int i, j;
	u_int32_t postcode = 0, configstatus = 0;
	ips_write_1(sc, COPPER_REG_SCPR, 0x80);
	ips_write_1(sc, COPPER_REG_SCPR, 0);
	device_printf(sc->dev, "reinitializing adapter, this could take several minutes.\n");
	for(j = 0; j < 2; j++){
		postcode <<= 8;
		for(i = 0; i < 45; i++){
			if(ips_read_1(sc, COPPER_REG_HISR) & COPPER_GHI_BIT){
				postcode |= ips_read_1(sc, COPPER_REG_ISPR);
				ips_write_1(sc, COPPER_REG_HISR, 
					    COPPER_GHI_BIT);
				break;
			} else
				DELAY(1000000);
		}
		if(i == 45)
			return 1;
	}
	for(j = 0; j < 2; j++){
		configstatus <<= 8;
		for(i = 0; i < 240; i++){
			if(ips_read_1(sc, COPPER_REG_HISR) & COPPER_GHI_BIT){
				configstatus |= ips_read_1(sc, COPPER_REG_ISPR);
				ips_write_1(sc, COPPER_REG_HISR, 
					    COPPER_GHI_BIT);
				break;
			} else
				DELAY(1000000);
		}
		if(i == 240)
			return 1;
	}
	for(i = 0; i < 240; i++){
		if(!(ips_read_1(sc, COPPER_REG_CBSP) & COPPER_OP_BIT)){
			break;
		} else
			DELAY(1000000);
	}
	if(i == 240)
		return 1;
	ips_write_2(sc, COPPER_REG_CCCR, 0x1000 | COPPER_ILE_BIT);
	ips_write_1(sc, COPPER_REG_SCPR, COPPER_EBM_BIT);
	ips_copperhead_queue_init(sc);
	ips_write_1(sc, COPPER_REG_HISR, COPPER_GHI_BIT);
	i = ips_read_1(sc, COPPER_REG_SCPR);
	ips_write_1(sc, COPPER_REG_HISR, COPPER_EI_BIT);
	if(!configstatus){
		device_printf(sc->dev, "adapter initialization failed\n");
		return 1;
	}
	if(force && ips_clear_adapter(sc)){
		device_printf(sc->dev, "adapter clear failed\n");
		return 1;
	}
	return 0;
}
static u_int32_t ips_copperhead_cmd_status(ips_softc_t *sc)
{
	intrmask_t mask;
	u_int32_t value;
	int statnum = sc->copper_queue->nextstatus++;
	if(sc->copper_queue->nextstatus == IPS_MAX_CMD_NUM)
		sc->copper_queue->nextstatus = 0;
	mask = splbio();
	value = sc->copper_queue->status[statnum];
	ips_write_4(sc, COPPER_REG_SQTR, sc->copper_queue->base_phys_addr + 
		    4 * statnum);
	splx(mask);
	return value;
}


void ips_copperhead_intr(void *void_sc)
{
        ips_softc_t *sc = (ips_softc_t *)void_sc;
	int cmdnumber;
	ips_cmd_status_t status;

	while(ips_read_1(sc, COPPER_REG_HISR) & COPPER_SCE_BIT){
		status.value = ips_copperhead_cmd_status(sc);
		cmdnumber = status.fields.command_id;
		sc->commandarray[cmdnumber].status.value = status.value;
		sc->commandarray[cmdnumber].timeout = 0;
		sc->commandarray[cmdnumber].callback(&(sc->commandarray[cmdnumber]));
		PRINTF(9, "ips: got command %d\n", cmdnumber);
	}
        return;
}

void ips_issue_copperhead_cmd(ips_command_t *command)
{
	int i;
	intrmask_t mask = splbio();
	/* hmmm, is there a cleaner way to do this? */
	if(command->sc->state & IPS_OFFLINE){
		splx(mask);
		command->status.value = IPS_ERROR_STATUS;
		command->callback(command);
		return;
	}
	command->timeout = 10;
	for(i = 0; ips_read_4(command->sc, COPPER_REG_CCCR) & COPPER_SEM_BIT;
	    i++ ){
		if( i == 20){
printf("sem bit still set, can't send a command\n");
			splx(mask);
			return;
		}
		DELAY(500);/* need to do a delay here */
	}
	ips_write_4(command->sc, COPPER_REG_CCSAR, command->command_phys_addr);
	ips_write_2(command->sc, COPPER_REG_CCCR, COPPER_CMD_START);
	splx(mask);
}

