/*	$OpenBSD: aacvar.h,v 1.1 2000/11/10 09:39:36 niklas Exp $	*/

/*-
 * Copyright (c) 2000 Michael Smith
 * Copyright (c) 2000 BSDi
 * Copyright (c) 2000 Niklas Hallqvist
 * 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.
 *
 *	$FreeBSD: /c/ncvs/src/sys/dev/aac/aacvar.h,v 1.1 2000/09/13 03:20:34 msmith Exp $
 */

/*
 * This driver would not have rewritten for OpenBSD if it was not for the
 * hardware dontion from Nocom.  I want to thank them for their support.
 * Of course, credit should go to Mike Smith for the original work he did
 * in the FreeBSD driver where I found lots of inspiration.
 * - Niklas Hallqvist
 */

/* Debugging */
#ifdef AAC_DEBUG
#define AAC_DPRINTF(mask, args) if (aac_debug & (mask)) printf args
#define AAC_D_INTR	0x01
#define AAC_D_MISC	0x02
#define AAC_D_CMD	0x04
#define AAC_D_QUEUE	0x08
#define AAC_D_IO	0x10
extern int aac_debug;

#define AAC_PRINT_FIB(sc, fib)	aac_print_fib((sc), (fib), __FUNCTION__)
#else
#define AAC_DPRINTF(mask, args)
#define AAC_PRINT_FIB(sc, fib)
#endif

struct aac_code_lookup {
	char	*string;
	u_int32_t code;
};

struct aac_softc;

/*
 * We allocate a small set of FIBs for the adapter to use to send us messages.
 */
#define AAC_ADAPTER_FIBS	8

/*
 * Firmware messages are passed in the printf buffer.
 */
#define AAC_PRINTF_BUFSIZE	256

/*
 * We wait this many seconds for the adapter to come ready if it is still
 * booting.
 */
#define AAC_BOOT_TIMEOUT	(3 * 60)

/*
 * Wait this long for a lost interrupt to get detected.
 */
#define AAC_WATCH_TIMEOUT	10000		/* 10000 * 1ms = 10s */

/*
 * Timeout for immediate commands.
 */
#define AAC_IMMEDIATE_TIMEOUT	30

/*
 * Delay 20ms after the qnotify in sync operations.  Experimentally deduced.
 */
#define AAC_SYNC_DELAY 20000

/*
 * The firmware interface allows for a 16-bit s/g list length.  We limit 
 * ourselves to a reasonable maximum and ensure alignment.
 */
#define AAC_MAXSGENTRIES	64	/* max S/G entries, limit 65535 */		
/*
 * We gather a number of adapter-visible items into a single structure.
 *
 * The ordering of this strucure may be important; we copy the Linux driver:
 *
 * Adapter FIBs
 * Init struct
 * Queue headers (Comm Area)
 * Printf buffer
 *
 * In addition, we add:
 * Sync Fib
 */
struct aac_common {
	/* fibs for the controller to send us messages */
	struct aac_fib ac_fibs[AAC_ADAPTER_FIBS];

	/* the init structure */
	struct aac_adapter_init	ac_init;

	/* arena within which the queue structures are kept */
	u_int8_t ac_qbuf[sizeof(struct aac_queue_table) + AAC_QUEUE_ALIGN];

	/* buffer for text messages from the controller */
	char	ac_printf[AAC_PRINTF_BUFSIZE];
    
	/* fib for synchronous commands */
	struct aac_fib ac_sync_fib;
};

/*
 * Interface operations
 */
struct aac_interface {
	int	(*aif_get_fwstatus) __P((struct aac_softc *));
	void	(*aif_qnotify) __P((struct aac_softc *, int));
	int	(*aif_get_istatus) __P((struct aac_softc *));
	void	(*aif_set_istatus) __P((struct aac_softc *, int));
	void	(*aif_set_mailbox) __P((struct aac_softc *, u_int32_t,
	    u_int32_t, u_int32_t, u_int32_t, u_int32_t));
	int	(*aif_get_mailboxstatus) __P((struct aac_softc *));
	void	(*aif_set_interrupts) __P((struct aac_softc *, int));
};
extern struct aac_interface aac_rx_interface;
extern struct aac_interface aac_sa_interface;

#define AAC_GET_FWSTATUS(sc)		((sc)->sc_if.aif_get_fwstatus(sc))
#define AAC_QNOTIFY(sc, qbit) \
	((sc)->sc_if.aif_qnotify((sc), (qbit)))
#define AAC_GET_ISTATUS(sc)		((sc)->sc_if.aif_get_istatus(sc))
#define AAC_CLEAR_ISTATUS(sc, mask) \
	((sc)->sc_if.aif_set_istatus((sc), (mask)))
#define AAC_SET_MAILBOX(sc, command, arg0, arg1, arg2, arg3) \
	do {								\
		((sc)->sc_if.aif_set_mailbox((sc), (command), (arg0),	\
		    (arg1), (arg2), (arg3)));				\
	} while(0)
#define AAC_GET_MAILBOXSTATUS(sc) \
	((sc)->sc_if.aif_get_mailboxstatus(sc))
#define	AAC_MASK_INTERRUPTS(sc)	\
	((sc)->sc_if.aif_set_interrupts((sc), 0))
#define AAC_UNMASK_INTERRUPTS(sc) \
	((sc)->sc_if.aif_set_interrupts((sc), 1))

#define AAC_SETREG4(sc, reg, val) \
	bus_space_write_4((sc)->sc_memt, (sc)->sc_memh, (reg), (val))
#define AAC_GETREG4(sc, reg) \
	bus_space_read_4((sc)->sc_memt, (sc)->sc_memh, (reg))
#define AAC_SETREG2(sc, reg, val) \
	bus_space_write_2((sc)->sc_memt, (sc)->sc_memh, (reg), (val))
#define AAC_GETREG2(sc, reg) \
	bus_space_read_2((sc)->sc_memt, (sc)->sc_memh, (reg))
#define AAC_SETREG1(sc, reg, val) \
	bus_space_write_1((sc)->sc_memt, (sc)->sc_memh, (reg), (val))
#define AAC_GETREG1(sc, reg) \
	bus_space_read_1((sc)->sc_memt, (sc)->sc_memh, (reg))

/*
 * Per-container data structure
 */
struct aac_container
{
	struct aac_mntobj co_mntobj;
	struct device co_disk;
};

/*
 * A command contol block, one for each corresponding command index of the
 * controller.
 */
struct aac_ccb {
	TAILQ_ENTRY(aac_ccb) ac_chain;
	struct scsi_xfer *ac_xs;
	struct aac_fib *ac_fib;		/* FIB associated with this command */
	bus_addr_t ac_fibphys;		/* bus address of the FIB */
	bus_dmamap_t ac_dmamap_xfer;
	struct aac_sg_table *ac_sgtable;/* pointer to s/g table in command */
	int ac_timeout;
	u_int32_t ac_blockno;
	u_int32_t ac_blockcnt;
	u_int8_t ac_flags;
#define AAC_ACF_WATCHDOG 	0x1
#define AAC_ACF_COMPLETED 	0x2
};

/*
 * Per-controller structure.
 */
struct aac_softc {
	struct device sc_dev;
	void   *sc_ih;
	struct	scsi_link sc_link;	/* Virtual SCSI bus for cache devs */

	bus_space_tag_t sc_memt;
	bus_space_handle_t sc_memh;
	bus_dma_tag_t sc_dmat;		/* parent DMA tag */

	/* controller features, limits and status */
	int	sc_state;
#define AAC_STATE_SUSPEND	(1<<0)
#define	AAC_STATE_OPEN		(1<<1)
#define AAC_STATE_INTERRUPTS_ON	(1<<2)
#define AAC_STATE_AIF_SLEEPER	(1<<3)
	struct FsaRevision sc_revision;

	int	sc_hwif;	/* controller hardware interface */
#define AAC_HWIF_I960RX		0
#define AAC_HWIF_STRONGARM	1

	struct aac_common *sc_common;
	u_int32_t sc_common_busaddr;
	struct aac_interface sc_if;

	/* XXX This should really be dynamic.  It is very wasteful now. */
	struct aac_ccb sc_ccbs[AAC_ADAP_NORM_CMD_ENTRIES];
	TAILQ_HEAD(, aac_ccb) sc_free_ccb, sc_ccbq;
	/* commands on hold for controller resources */
	TAILQ_HEAD(, aac_ccb) sc_ready;
	/* commands which have been returned by the controller */
	TAILQ_HEAD(, aac_ccb) sc_completed;
	LIST_HEAD(, scsi_xfer) sc_queue;
	struct scsi_xfer *sc_queuelast;

	/* command management */
	struct aac_queue_table *sc_queues;
	struct aac_queue_entry *sc_qentries[AAC_QUEUE_COUNT];

	struct {
		u_int8_t hd_present;
		u_int8_t hd_is_logdrv;
		u_int8_t hd_is_arraydrv;
		u_int8_t hd_is_master;
		u_int8_t hd_is_parity;
		u_int8_t hd_is_hotfix;
		u_int8_t hd_master_no;
		u_int8_t hd_lock;
		u_int8_t hd_heads;
		u_int8_t hd_secs;
		u_int16_t hd_devtype;
		u_int32_t hd_size;
		u_int8_t hd_ldr_no;
		u_int8_t hd_rw_attribs;
		u_int32_t hd_start_sec;
	} sc_hdr[AAC_MAX_CONTAINERS];
};

/* XXX These have to become spinlocks in case of SMP */
#define AAC_LOCK(sc) splbio()
#define AAC_UNLOCK(sc, lock) splx(lock)
typedef int aac_lock_t;

void	aacminphys __P((struct buf *));
int	aac_attach __P((struct aac_softc *));
int	aac_intr __P((void *));

#ifdef __GNUC__
/* These all require correctly aligned buffers */
static __inline__ void aac_enc16 __P((u_int8_t *, u_int16_t));
static __inline__ void aac_enc32 __P((u_int8_t *, u_int32_t));
static __inline__ u_int16_t aac_dec16 __P((u_int8_t *));
static __inline__ u_int32_t aac_dec32 __P((u_int8_t *));

static __inline__ void
aac_enc16(addr, value)
	u_int8_t *addr;
	u_int16_t value;
{
	*(u_int16_t *)addr = htole16(value);
}

static __inline__ void
aac_enc32(addr, value)
	u_int8_t *addr;
	u_int32_t value;
{
	*(u_int32_t *)addr = htole32(value);
}

static __inline__ u_int16_t
aac_dec16(addr)
	u_int8_t *addr;
{
	return letoh16(*(u_int16_t *)addr);
}

static __inline__ u_int32_t
aac_dec32(addr)
	u_int8_t *addr;
{
	return letoh32(*(u_int32_t *)addr);
}

/*
 * Queue primitives
 *
 * These are broken out individually to make statistics gathering easier.
 */

static __inline__ void
aac_enqueue_completed(struct aac_ccb *ccb)
{
	struct aac_softc *sc = ccb->ac_xs->sc_link->adapter_softc;
	aac_lock_t lock;

	lock = AAC_LOCK(sc);
	TAILQ_INSERT_TAIL(&sc->sc_completed, ccb, ac_chain);
	AAC_UNLOCK(sc, lock);
}

static __inline__ struct aac_ccb *
aac_dequeue_completed(struct aac_softc *sc)
{
	struct aac_ccb *ccb;
	aac_lock_t lock;

	lock = AAC_LOCK(sc);
	if ((ccb = TAILQ_FIRST(&sc->sc_completed)) != NULL)
		TAILQ_REMOVE(&sc->sc_completed, ccb, ac_chain);
	AAC_UNLOCK(sc, lock);
	return (ccb);
}
#endif
