/*
 *  sata_nv.c - NVIDIA nForce SATA
 *
 *  Copyright 2004 NVIDIA Corp.  All rights reserved.
 *  Copyright 2004 Andrew Chew
 *
 *  The contents of this file are subject to the Open
 *  Software License version 1.1 that can be found at
 *  http://www.opensource.org/licenses/osl-1.1.txt and is included herein
 *  by reference.
 *
 *  Alternatively, the contents of this file may be used under the terms
 *  of the GNU General Public License version 2 (the "GPL") as distributed
 *  in the kernel source COPYING file, in which case the provisions of
 *  the GPL are applicable instead of the above.  If you wish to allow
 *  the use of your version of this file only under the terms of the
 *  GPL and not to allow others to use your version of this file under
 *  the OSL, indicate your decision by deleting the provisions above and
 *  replace them with the notice and other provisions required by the GPL.
 *  If you do not delete the provisions above, a recipient may use your
 *  version of this file under either the OSL or the GPL.
 *
 *  0.11
 *     - Added sgpio support
 *
 *  0.10
 *     - Fixed spurious interrupts issue seen with the Maxtor 6H500F0 500GB
 *       drive.  Also made the check_hotplug() callbacks return whether there
 *       was a hotplug interrupt or not.  This was not the source of the
 *       spurious interrupts, but is the right thing to do anyway.
 *
 *  0.09
 *     - Fixed bug introduced by 0.08's MCP51 and MCP55 support.
 *
 *  0.08
 *     - Added support for MCP51 and MCP55.
 *
 *  0.07
 *     - Added support for RAID class code.
 *
 *  0.06
 *     - Added generic SATA support by using a pci_device_id that filters on
 *       the IDE storage class code.
 *
 *  0.03
 *     - Fixed a bug where the hotplug handlers for non-CK804/MCP04 were using
 *       mmio_base, which is only set for the CK804/MCP04 case.
 *
 *  0.02
 *     - Added support for CK804 SATA controller.
 *
 *  0.01
 *     - Initial revision.
 */

#include <linux/config.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include "scsi.h"
#include <scsi/scsi_host.h>
#include <linux/libata.h>

#define DRV_NAME			"sata_nv"
#define DRV_VERSION			"0.11-Driver Package V1.24"

#define NV_PORTS			2
#define NV_PIO_MASK			0x1f
#define NV_MWDMA_MASK			0x07
#define NV_UDMA_MASK			0x7f
#define NV_PORT0_SCR_REG_OFFSET		0x00
#define NV_PORT1_SCR_REG_OFFSET		0x40

#define NV_INT_STATUS			0x10
#define NV_INT_STATUS_CK804		0x440
#define NV_INT_STATUS_MCP55		0x440
#define NV_INT_STATUS_PDEV_INT		0x01
#define NV_INT_STATUS_PDEV_PM		0x02
#define NV_INT_STATUS_PDEV_ADDED	0x04
#define NV_INT_STATUS_PDEV_REMOVED	0x08
#define NV_INT_STATUS_SDEV_INT		0x10
#define NV_INT_STATUS_SDEV_PM		0x20
#define NV_INT_STATUS_SDEV_ADDED	0x40
#define NV_INT_STATUS_SDEV_REMOVED	0x80
#define NV_INT_STATUS_PDEV_HOTPLUG	(NV_INT_STATUS_PDEV_ADDED | \
					NV_INT_STATUS_PDEV_REMOVED)
#define NV_INT_STATUS_SDEV_HOTPLUG	(NV_INT_STATUS_SDEV_ADDED | \
					NV_INT_STATUS_SDEV_REMOVED)
#define NV_INT_STATUS_HOTPLUG		(NV_INT_STATUS_PDEV_HOTPLUG | \
					NV_INT_STATUS_SDEV_HOTPLUG)

#define NV_INT_ENABLE			0x11
#define NV_INT_ENABLE_CK804		0x441
#define NV_INT_ENABLE_MCP55		0x444
#define NV_INT_ENABLE_PDEV_MASK		0x01
#define NV_INT_ENABLE_PDEV_PM		0x02
#define NV_INT_ENABLE_PDEV_ADDED	0x04
#define NV_INT_ENABLE_PDEV_REMOVED	0x08
#define NV_INT_ENABLE_SDEV_MASK		0x10
#define NV_INT_ENABLE_SDEV_PM		0x20
#define NV_INT_ENABLE_SDEV_ADDED	0x40
#define NV_INT_ENABLE_SDEV_REMOVED	0x80
#define NV_INT_ENABLE_PDEV_HOTPLUG	(NV_INT_ENABLE_PDEV_ADDED | \
					NV_INT_ENABLE_PDEV_REMOVED)
#define NV_INT_ENABLE_SDEV_HOTPLUG	(NV_INT_ENABLE_SDEV_ADDED | \
					NV_INT_ENABLE_SDEV_REMOVED)
#define NV_INT_ENABLE_HOTPLUG		(NV_INT_ENABLE_PDEV_HOTPLUG | \
					NV_INT_ENABLE_SDEV_HOTPLUG)

#define NV_INT_CONFIG			0x12
#define NV_INT_CONFIG_METHD		0x01 // 0 = INT, 1 = SMI
#define PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SATA	0x037E
#define PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SATA2	0x037F

// For PCI config register 20
#define NV_MCP_SATA_CFG_20		0x50
#define NV_MCP_SATA_CFG_20_SATA_SPACE_EN	0x04


#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
#define RHAS3U7
#endif
#if LINUX_VERSION_CODE == KERNEL_VERSION(2,6,16)
#define SLES10
#endif

//sgpio
// Sgpio defines
// SGPIO state defines
#define NV_SGPIO_STATE_RESET		0
#define NV_SGPIO_STATE_OPERATIONAL	1
#define NV_SGPIO_STATE_ERROR		2

// SGPIO command opcodes
#define NV_SGPIO_CMD_RESET		0
#define NV_SGPIO_CMD_READ_PARAMS	1
#define NV_SGPIO_CMD_READ_DATA		2
#define NV_SGPIO_CMD_WRITE_DATA		3

// SGPIO command status defines
#define NV_SGPIO_CMD_OK			0
#define NV_SGPIO_CMD_ACTIVE		1
#define NV_SGPIO_CMD_ERR		2

#define NV_SGPIO_UPDATE_TICK		90
#define NV_SGPIO_MIN_UPDATE_DELTA	33
#define NV_CNTRLR_SHARE_INIT		2
#define NV_SGPIO_MAX_ACTIVITY_ON	20
#define NV_SGPIO_MIN_FORCE_OFF		5
#define NV_SGPIO_PCI_CSR_OFFSET		0x58
#define NV_SGPIO_PCI_CB_OFFSET		0x5C
#define NV_SGPIO_DFLT_CB_SIZE		256
#define NV_ON 1
#define NV_OFF 0
#ifndef bool
#define bool u8
#endif

static inline unsigned int jiffies_to_msecs1(const unsigned long j)
{
#if HZ <= 1000 && !(1000 % HZ)
        return (1000 / HZ) * j;
#elif HZ > 1000 && !(HZ % 1000)
        return (j + (HZ / 1000) - 1)/(HZ / 1000);
#else
        return (j * 1000) / HZ;
#endif
}

#define BF_EXTRACT(v, off, bc)	\
	((((u8)(v)) >> (off)) & ((1 << (bc)) - 1))

#define BF_INS(v, ins, off, bc)				\
	(((v) & ~((((1 << (bc)) - 1)) << (off))) |	\
	(((u8)(ins)) << (off)))

#define BF_EXTRACT_U32(v, off, bc)	\
	((((u32)(v)) >> (off)) & ((1 << (bc)) - 1))

#define BF_INS_U32(v, ins, off, bc)			\
	(((v) & ~((((1 << (bc)) - 1)) << (off))) |	\
	(((u32)(ins)) << (off)))

#define GET_SGPIO_STATUS(v)	BF_EXTRACT(v, 0, 2)
#define GET_CMD_STATUS(v)	BF_EXTRACT(v, 3, 2)
#define GET_CMD(v)		BF_EXTRACT(v, 5, 3)
#define SET_CMD(v, cmd)		BF_INS(v, cmd, 5, 3) 

#define GET_ENABLE(v)		BF_EXTRACT_U32(v, 23, 1)
#define SET_ENABLE(v)		BF_INS_U32(v, 1, 23, 1)

// Needs to have a u8 bit-field insert.
#define GET_ACTIVITY(v)		BF_EXTRACT(v, 5, 3)
#define SET_ACTIVITY(v, on_off)	BF_INS(v, on_off, 5, 3)

union nv_sgpio_nvcr 
{
	struct {
		u8	init_cnt;
		u8	cb_size;
		u8	cbver;
		u8	rsvd;
	} bit;
	u32	all;
};

union nv_sgpio_tx 
{
	u8	tx_port[4];
	u32 	all;
};

struct nv_sgpio_cb 
{
	u64			scratch_space;
	union nv_sgpio_nvcr	nvcr;
	u32	cr0;
	u32                     rsvd[4];
	union nv_sgpio_tx       tx[2];
};

struct nv_sgpio_host_share
{
	spinlock_t	*plock;
	unsigned long   *ptstamp;
};

struct nv_sgpio_host_flags
{
	u8	sgpio_enabled:1;
	u8	need_update:1;
	u8	rsvd:6;
};
	
struct nv_host_sgpio
{
	struct nv_sgpio_host_flags	flags;
	u8				*pcsr;
	struct nv_sgpio_cb		*pcb;	
	struct nv_sgpio_host_share	share;
	struct timer_list		sgpio_timer;
};

struct nv_sgpio_port_flags
{
	u8	last_state:1;
	u8	recent_activity:1;
	u8	rsvd:6;
};

struct nv_sgpio_led 
{
	struct nv_sgpio_port_flags	flags;
	u8				force_off;
	u8      			last_cons_active;
};

struct nv_port_sgpio
{
	struct nv_sgpio_led	activity;
};

static spinlock_t	nv_sgpio_lock;
static unsigned long	nv_sgpio_tstamp;

static inline void nv_sgpio_set_csr(u8 csr, unsigned long pcsr)
{
	outb(csr, pcsr);
}

static inline u8 nv_sgpio_get_csr(unsigned long pcsr)
{
	return inb(pcsr);
}

static inline u8 nv_sgpio_get_func(struct ata_host_set *host_set)
{
	u8 devfn = (to_pci_dev(host_set->dev))->devfn;
	return (PCI_FUNC(devfn));
}

static inline u8 nv_sgpio_tx_host_offset(struct ata_host_set *host_set)
{
	return (nv_sgpio_get_func(host_set)/NV_CNTRLR_SHARE_INIT);
}

static inline u8 nv_sgpio_calc_tx_offset(u8 cntrlr, u8 channel)
{
	return (sizeof(union nv_sgpio_tx) - (NV_CNTRLR_SHARE_INIT *
		(cntrlr % NV_CNTRLR_SHARE_INIT)) - channel - 1);
}

static inline u8 nv_sgpio_tx_port_offset(struct ata_port *ap)
{
	u8 cntrlr = nv_sgpio_get_func(ap->host_set);
	return (nv_sgpio_calc_tx_offset(cntrlr, ap->port_no));
}

static inline bool nv_sgpio_capable(const struct pci_device_id *ent)
{
	if (ent->device == PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SATA2)
		return 1;
	else
		return 0;
}






static int nv_init_one (struct pci_dev *pdev, const struct pci_device_id *ent);
static irqreturn_t nv_interrupt (int irq, void *dev_instance,
				 struct pt_regs *regs);
static u32 nv_scr_read (struct ata_port *ap, unsigned int sc_reg);
static void nv_scr_write (struct ata_port *ap, unsigned int sc_reg, u32 val);
static void nv_host_stop (struct ata_host_set *host_set);
static int nv_port_start(struct ata_port *ap);
static void nv_port_stop(struct ata_port *ap);
static int nv_qc_issue(struct ata_queued_cmd *qc);
static void nv_enable_hotplug(struct ata_probe_ent *probe_ent);
static void nv_disable_hotplug(struct ata_host_set *host_set);
static void nv_check_hotplug(struct ata_host_set *host_set);
static void nv_enable_hotplug_ck804(struct ata_probe_ent *probe_ent);
static void nv_disable_hotplug_ck804(struct ata_host_set *host_set);
static void nv_check_hotplug_ck804(struct ata_host_set *host_set);
static void nv_enable_hotplug_mcp55(struct ata_probe_ent *probe_ent);
static void nv_disable_hotplug_mcp55(struct ata_host_set *host_set);
static void nv_check_hotplug_mcp55(struct ata_host_set *host_set);
enum nv_host_type
{
	GENERIC,
	NFORCE2,
	NFORCE3,
	CK804,
	MCP55
};

static struct pci_device_id nv_pci_tbl[] = {
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE2S_SATA,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, NFORCE2 },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE3S_SATA,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, NFORCE3 },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE3S_SATA2,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, NFORCE3 },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_CK804_SATA,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, CK804 },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_CK804_SATA2,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, CK804 },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP04_SATA,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, CK804 },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP04_SATA2,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, CK804 },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SATA,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, MCP55 },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SATA2,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, MCP55 },
	{ PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID,
		PCI_ANY_ID, PCI_ANY_ID,
		PCI_CLASS_STORAGE_IDE<<8, 0xffff00, GENERIC },
	{ 0, } /* terminate list */
};

#define NV_HOST_FLAGS_SCR_MMIO	0x00000001

struct nv_host_desc
{
	enum nv_host_type	host_type;
	void			(*enable_hotplug)(struct ata_probe_ent *probe_ent);
	void			(*disable_hotplug)(struct ata_host_set *host_set);
	void			(*check_hotplug)(struct ata_host_set *host_set);

};
static struct nv_host_desc nv_device_tbl[] = {
	{
		.host_type	= GENERIC,
		.enable_hotplug	= NULL,
		.disable_hotplug= NULL,
		.check_hotplug	= NULL,
	},
	{
		.host_type	= NFORCE2,
		.enable_hotplug	= nv_enable_hotplug,
		.disable_hotplug= nv_disable_hotplug,
		.check_hotplug	= nv_check_hotplug,
	},
	{
		.host_type	= NFORCE3,
		.enable_hotplug	= nv_enable_hotplug,
		.disable_hotplug= nv_disable_hotplug,
		.check_hotplug	= nv_check_hotplug,
	},
	{	.host_type	= CK804,
		.enable_hotplug	= nv_enable_hotplug_ck804,
		.disable_hotplug= nv_disable_hotplug_ck804,
		.check_hotplug	= nv_check_hotplug_ck804,
	},
	{	.host_type	= MCP55,
		.enable_hotplug	= nv_enable_hotplug_mcp55,
		.disable_hotplug= nv_disable_hotplug_mcp55,
		.check_hotplug	= nv_check_hotplug_mcp55,
	},
};


struct nv_host
{
	struct nv_host_desc	*host_desc;
	unsigned long		host_flags;
  	struct nv_host_sgpio 	host_sgpio;
	struct pci_dev		*pdev;
};

struct nv_port
{
    	struct nv_port_sgpio	port_sgpio;
};

// SGPIO function prototypes
static void nv_sgpio_init(struct pci_dev *pdev, struct nv_host *phost);
static void nv_sgpio_reset(u8 *pcsr);
static void nv_sgpio_set_timer(struct timer_list *ptimer, 
				unsigned int timeout_msec);
static void nv_sgpio_timer_handler(unsigned long ptr);
static void nv_sgpio_host_cleanup(struct nv_host *host);
static bool nv_sgpio_update_led(struct nv_sgpio_led *led, bool *on_off);
static void nv_sgpio_clear_all_leds(struct ata_port *ap);
static bool nv_sgpio_send_cmd(struct nv_host *host, u8 cmd);


static struct pci_driver nv_pci_driver = {
	.name			= DRV_NAME,
	.id_table		= nv_pci_tbl,
	.probe			= nv_init_one,
	.remove			= ata_pci_remove_one,
};


#ifdef SLES10
static  struct scsi_host_template nv_sht = {
#else
static  Scsi_Host_Template nv_sht = {
#endif
	.module			= THIS_MODULE,
	.name			= DRV_NAME,
#ifdef RHAS3U7
	.detect			= ata_scsi_detect,
	.release		= ata_scsi_release,
#endif
	.ioctl			= ata_scsi_ioctl,
	.queuecommand		= ata_scsi_queuecmd,
	.eh_strategy_handler	= ata_scsi_error,
	.can_queue		= ATA_DEF_QUEUE,
	.this_id		= ATA_SHT_THIS_ID,
	.sg_tablesize		= LIBATA_MAX_PRD,
	.max_sectors		= ATA_MAX_SECTORS,
	.cmd_per_lun		= ATA_SHT_CMD_PER_LUN,
#ifdef RHAS3U7
	.use_new_eh_code	= ATA_SHT_NEW_EH_CODE,
#endif
	.emulated		= ATA_SHT_EMULATED,
	.use_clustering		= ATA_SHT_USE_CLUSTERING,
	.proc_name		= DRV_NAME,
#ifndef RHAS3U7
    	.dma_boundary		= ATA_DMA_BOUNDARY,
	.slave_configure	= ata_scsi_slave_config,
#endif
	.bios_param		= ata_std_bios_param,
};

static struct ata_port_operations nv_ops = {
	.port_disable		= ata_port_disable,
	.tf_load		= ata_tf_load,
	.tf_read		= ata_tf_read,
	.exec_command		= ata_exec_command,
	.check_status		= ata_check_status,
	.dev_select		= ata_std_dev_select,
	.phy_reset		= sata_phy_reset,
	.bmdma_setup		= ata_bmdma_setup,
	.bmdma_start		= ata_bmdma_start,
	.bmdma_stop		= ata_bmdma_stop,
	.bmdma_status		= ata_bmdma_status,
	.qc_prep		= ata_qc_prep,
	.qc_issue		= nv_qc_issue,
	.eng_timeout		= ata_eng_timeout,
	.irq_handler		= nv_interrupt,
	.irq_clear		= ata_bmdma_irq_clear,
	.scr_read		= nv_scr_read,
	.scr_write		= nv_scr_write,
	.port_start		= nv_port_start,
	.port_stop		= nv_port_stop,
	.host_stop		= nv_host_stop,
};

/* FIXME: The hardware provides the necessary SATA PHY controls
 * to support ATA_FLAG_SATA_RESET.  However, it is currently
 * necessary to disable that flag, to solve misdetection problems.
 * See http://bugme.osdl.org/show_bug.cgi?id=3352 for more info.
 *
 * This problem really needs to be investigated further.  But in the
 * meantime, we avoid ATA_FLAG_SATA_RESET to get people working.
 */
static struct ata_port_info nv_port_info = {
	.sht		= &nv_sht,
	.host_flags	= ATA_FLAG_SATA |
			  /* ATA_FLAG_SATA_RESET | */
			  ATA_FLAG_SRST |
			  ATA_FLAG_NO_LEGACY,
	.pio_mask	= NV_PIO_MASK,
	.mwdma_mask	= NV_MWDMA_MASK,
	.udma_mask	= NV_UDMA_MASK,
	.port_ops	= &nv_ops,
};

MODULE_AUTHOR("NVIDIA");
MODULE_DESCRIPTION("low-level driver for NVIDIA nForce SATA controller");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(pci, nv_pci_tbl);
MODULE_VERSION(DRV_VERSION);

static irqreturn_t nv_interrupt (int irq, void *dev_instance,
				 struct pt_regs *regs)
{
	struct ata_host_set *host_set = dev_instance;
	struct nv_host *host = host_set->private_data;
	unsigned int i;
	unsigned int handled = 0;
	unsigned long flags;

	spin_lock_irqsave(&host_set->lock, flags);

	for (i = 0; i < host_set->n_ports; i++) {
		struct ata_port *ap;

		ap = host_set->ports[i];
#ifdef ATA_FLAG_NOINTR
        if (ap &&
		    !(ap->flags & (ATA_FLAG_PORT_DISABLED | ATA_FLAG_NOINTR))) {
#else
        if (ap && (!(ap->flags & ATA_FLAG_PORT_DISABLED))) {
#endif    
            struct ata_queued_cmd *qc;

			qc = ata_qc_from_tag(ap, ap->active_tag);
			if (qc && (!(qc->tf.ctl & ATA_NIEN)))
				handled += ata_host_intr(ap, qc);
			else
				// No request pending?  Clear interrupt status
				// anyway, in case there's one pending.
				ap->ops->check_status(ap);
		}

	}

	if (host->host_desc->check_hotplug)
		host->host_desc->check_hotplug(host_set);

	spin_unlock_irqrestore(&host_set->lock, flags);

	return IRQ_RETVAL(handled);
}

static u32 nv_scr_read (struct ata_port *ap, unsigned int sc_reg)
{
	struct ata_host_set *host_set = ap->host_set;
	struct nv_host *host = host_set->private_data;

	if (sc_reg > SCR_CONTROL)
		return 0xffffffffU;

	if (host->host_flags & NV_HOST_FLAGS_SCR_MMIO)
		return readl((void*)ap->ioaddr.scr_addr + (sc_reg * 4));
	else
		return inl(ap->ioaddr.scr_addr + (sc_reg * 4));
}

static void nv_scr_write (struct ata_port *ap, unsigned int sc_reg, u32 val)
{
	struct ata_host_set *host_set = ap->host_set;
	struct nv_host *host = host_set->private_data;

	if (sc_reg > SCR_CONTROL)
		return;

	if (host->host_flags & NV_HOST_FLAGS_SCR_MMIO)
		writel(val, (void*)ap->ioaddr.scr_addr + (sc_reg * 4));
	else
		outl(val, ap->ioaddr.scr_addr + (sc_reg * 4));
}

static void nv_host_stop (struct ata_host_set *host_set)
{
	struct nv_host *host = host_set->private_data;

	// Disable hotplug event interrupts.
	if (host->host_desc->disable_hotplug)
		host->host_desc->disable_hotplug(host_set);

	nv_sgpio_host_cleanup(host);
	kfree(host);
#ifdef RHAS3U7

	ata_host_stop(host_set);
#endif
}

static int nv_init_one (struct pci_dev *pdev, const struct pci_device_id *ent)
{
	static int printed_version = 0;
	struct nv_host *host;
	struct ata_port_info *ppi;
	struct ata_probe_ent *probe_ent;
	int pci_dev_busy = 0;
	int rc;
	u32 bar;

        // Make sure this is a SATA controller by counting the number of bars
        // (NVIDIA SATA controllers will always have six bars).  Otherwise,
        // it's an IDE controller and we ignore it.
	for (bar=0; bar<6; bar++)
		if (pci_resource_start(pdev, bar) == 0)
			return -ENODEV;

	if (!printed_version++)
		printk(KERN_DEBUG DRV_NAME " version " DRV_VERSION "\n");

	rc = pci_enable_device(pdev);
	if (rc)
		goto err_out;

	rc = pci_request_regions(pdev, DRV_NAME);
	if (rc) {
		pci_dev_busy = 1;
		goto err_out_disable;
	}

	rc = pci_set_dma_mask(pdev, ATA_DMA_MASK);
	if (rc)
		goto err_out_regions;
#ifndef RHAS3U7
    rc = pci_set_consistent_dma_mask(pdev, ATA_DMA_MASK);
	if (rc)
		goto err_out_regions;
#endif
	rc = -ENOMEM;

	ppi = &nv_port_info;

  probe_ent = ata_pci_init_native_mode(pdev, &ppi, ATA_PORT_PRIMARY|ATA_PORT_SECONDARY);

  if (!probe_ent)
		goto err_out_regions;

	host = kmalloc(sizeof(struct nv_host), GFP_KERNEL);
	if (!host)
		goto err_out_free_ent;

	memset(host, 0, sizeof(struct nv_host));
	host->host_desc = &nv_device_tbl[ent->driver_data];

	probe_ent->private_data = host;

	if (pci_resource_flags(pdev, 5) & IORESOURCE_MEM)
		host->host_flags |= NV_HOST_FLAGS_SCR_MMIO;

	if (host->host_flags & NV_HOST_FLAGS_SCR_MMIO) {
		unsigned long base;

		probe_ent->mmio_base = ioremap(pci_resource_start(pdev, 5),
				pci_resource_len(pdev, 5));
		if (probe_ent->mmio_base == NULL) {
			rc = -EIO;
			goto err_out_free_host;
		}

		base = (unsigned long)probe_ent->mmio_base;

		probe_ent->port[0].scr_addr =
			base + NV_PORT0_SCR_REG_OFFSET;
		probe_ent->port[1].scr_addr =
			base + NV_PORT1_SCR_REG_OFFSET;
	} else {

		probe_ent->port[0].scr_addr =
			pci_resource_start(pdev, 5) | NV_PORT0_SCR_REG_OFFSET;
		probe_ent->port[1].scr_addr =
			pci_resource_start(pdev, 5) | NV_PORT1_SCR_REG_OFFSET;
	}

	pci_set_master(pdev);
#ifdef RHAS3U7
	ata_add_to_probe_list(probe_ent);
    	
    	if (nv_sgpio_capable(ent))
		nv_sgpio_init(pdev, host);
	// Enable hotplug event interrupts.
	if (host->host_desc->enable_hotplug)
		host->host_desc->enable_hotplug(probe_ent);

	return 0;
#else
    	rc = ata_device_add(probe_ent);
	if (rc != NV_PORTS)
		goto err_out_iounmap;
    	
    	if (nv_sgpio_capable(ent))
		nv_sgpio_init(pdev, host);
	// Enable hotplug event interrupts.
	if (host->host_desc->enable_hotplug)
		host->host_desc->enable_hotplug(probe_ent);

	kfree(probe_ent);

	return 0;

err_out_iounmap:
	if (host->host_flags & NV_HOST_FLAGS_SCR_MMIO)
		iounmap(probe_ent->mmio_base);
#endif
err_out_free_host:
	kfree(host);
err_out_free_ent:
	kfree(probe_ent);
err_out_regions:
	pci_release_regions(pdev);
err_out_disable:
	if (!pci_dev_busy)
		pci_disable_device(pdev);
err_out:
	return rc;
}


static int nv_port_start(struct ata_port *ap)
{
	int stat;
    	struct nv_port *port;

    	stat = ata_port_start(ap);
    	if (stat) {
        	return stat;
    	}

    	port = kmalloc(sizeof(struct nv_port), GFP_KERNEL);
    	if (!port) 
		goto err_out_no_free;

	memset(port, 0, sizeof(struct nv_port));

    	ap->private_data = port;
    	return 0;

err_out_no_free:
    	return 1;
}

static void nv_port_stop(struct ata_port *ap)
{
	nv_sgpio_clear_all_leds(ap);

	if (ap->private_data) {
		kfree(ap->private_data);
		ap->private_data = NULL;
	}
	ata_port_stop(ap);
}

static int nv_qc_issue(struct ata_queued_cmd *qc)
{
	struct nv_port *port = qc->ap->private_data;

	if (port) 
		port->port_sgpio.activity.flags.recent_activity = 1;
	return (ata_qc_issue_prot(qc));
}




static void nv_enable_hotplug(struct ata_probe_ent *probe_ent)
{
	u8 intr_mask;

	outb(NV_INT_STATUS_HOTPLUG,
		probe_ent->port[0].scr_addr + NV_INT_STATUS);

	intr_mask = inb(probe_ent->port[0].scr_addr + NV_INT_ENABLE);
	intr_mask |= NV_INT_ENABLE_HOTPLUG;

	outb(intr_mask, probe_ent->port[0].scr_addr + NV_INT_ENABLE);
}

static void nv_disable_hotplug(struct ata_host_set *host_set)
{
	u8 intr_mask;

	intr_mask = inb(host_set->ports[0]->ioaddr.scr_addr + NV_INT_ENABLE);

	intr_mask &= ~(NV_INT_ENABLE_HOTPLUG);

	outb(intr_mask, host_set->ports[0]->ioaddr.scr_addr + NV_INT_ENABLE);
}

static void nv_check_hotplug(struct ata_host_set *host_set)
{
	u8 intr_status;

	intr_status = inb(host_set->ports[0]->ioaddr.scr_addr + NV_INT_STATUS);

	// Clear interrupt status.
	outb(0xff, host_set->ports[0]->ioaddr.scr_addr + NV_INT_STATUS);

	if (intr_status & NV_INT_STATUS_HOTPLUG) {
		if (intr_status & NV_INT_STATUS_PDEV_ADDED)
			printk(KERN_WARNING "nv_sata: "
				"Primary device added\n");

		if (intr_status & NV_INT_STATUS_PDEV_REMOVED)
			printk(KERN_WARNING "nv_sata: "
				"Primary device removed\n");

		if (intr_status & NV_INT_STATUS_SDEV_ADDED)
			printk(KERN_WARNING "nv_sata: "
				"Secondary device added\n");

		if (intr_status & NV_INT_STATUS_SDEV_REMOVED)
			printk(KERN_WARNING "nv_sata: "
				"Secondary device removed\n");
	}
}

static void nv_enable_hotplug_ck804(struct ata_probe_ent *probe_ent)
{
	struct pci_dev *pdev = to_pci_dev(probe_ent->dev);
	u8 intr_mask;
	u8 regval;

	pci_read_config_byte(pdev, NV_MCP_SATA_CFG_20, &regval);
	regval |= NV_MCP_SATA_CFG_20_SATA_SPACE_EN;
	pci_write_config_byte(pdev, NV_MCP_SATA_CFG_20, regval);

	writeb(NV_INT_STATUS_HOTPLUG, probe_ent->mmio_base + NV_INT_STATUS_CK804);

	intr_mask = readb(probe_ent->mmio_base + NV_INT_ENABLE_CK804);
	intr_mask |= NV_INT_ENABLE_HOTPLUG;

	writeb(intr_mask, probe_ent->mmio_base + NV_INT_ENABLE_CK804);
}

static void nv_disable_hotplug_ck804(struct ata_host_set *host_set)
{
	struct pci_dev *pdev = to_pci_dev(host_set->dev);
	u8 intr_mask;
	u8 regval;

	intr_mask = readb(host_set->mmio_base + NV_INT_ENABLE_CK804);

	intr_mask &= ~(NV_INT_ENABLE_HOTPLUG);

	writeb(intr_mask, host_set->mmio_base + NV_INT_ENABLE_CK804);

	pci_read_config_byte(pdev, NV_MCP_SATA_CFG_20, &regval);
	regval &= ~NV_MCP_SATA_CFG_20_SATA_SPACE_EN;
	pci_write_config_byte(pdev, NV_MCP_SATA_CFG_20, regval);
}

static void nv_check_hotplug_ck804(struct ata_host_set *host_set)
{
	u8 intr_status;

	intr_status = readb(host_set->mmio_base + NV_INT_STATUS_CK804);

	// Clear interrupt status.
	writeb(0xff, host_set->mmio_base + NV_INT_STATUS_CK804);

	if (intr_status & NV_INT_STATUS_HOTPLUG) {
		if (intr_status & NV_INT_STATUS_PDEV_ADDED)
			printk(KERN_WARNING "nv_sata: "
				"Primary device added\n");

		if (intr_status & NV_INT_STATUS_PDEV_REMOVED)
			printk(KERN_WARNING "nv_sata: "
				"Primary device removed\n");

		if (intr_status & NV_INT_STATUS_SDEV_ADDED)
			printk(KERN_WARNING "nv_sata: "
				"Secondary device added\n");

		if (intr_status & NV_INT_STATUS_SDEV_REMOVED)
			printk(KERN_WARNING "nv_sata: "
				"Secondary device removed\n");
	}
}
static void nv_enable_hotplug_mcp55(struct ata_probe_ent *probe_ent)
{
	struct pci_dev *pdev = to_pci_dev(probe_ent->dev);
	u8 intr_mask;
	u8 regval;

	pci_read_config_byte(pdev, NV_MCP_SATA_CFG_20, &regval);
	regval |= NV_MCP_SATA_CFG_20_SATA_SPACE_EN;
	pci_write_config_byte(pdev, NV_MCP_SATA_CFG_20, regval);

	writeb(0x0c, probe_ent->mmio_base + NV_INT_STATUS_MCP55);
	writeb(0x0c, probe_ent->mmio_base + NV_INT_STATUS_MCP55+2);

	intr_mask = readb(probe_ent->mmio_base + NV_INT_ENABLE_MCP55);
	intr_mask |= 0x0c;
	writeb(intr_mask, probe_ent->mmio_base + NV_INT_ENABLE_MCP55);

	intr_mask = readb(probe_ent->mmio_base + NV_INT_ENABLE_MCP55+2);
	intr_mask |= 0x0c;
	writeb(intr_mask, probe_ent->mmio_base + NV_INT_ENABLE_MCP55+2);
}

static void nv_disable_hotplug_mcp55(struct ata_host_set *host_set)
{
	struct pci_dev *pdev = to_pci_dev(host_set->dev);
	u8 intr_mask;
	u8 regval;

	intr_mask = readb(host_set->mmio_base + NV_INT_ENABLE_MCP55);
	intr_mask &= ~(0x0C);
	writeb(intr_mask, host_set->mmio_base + NV_INT_ENABLE_MCP55);
	
	intr_mask = readb(host_set->mmio_base + NV_INT_ENABLE_MCP55+2);
	intr_mask &= ~(0x0C);
	writeb(intr_mask, host_set->mmio_base + NV_INT_ENABLE_MCP55+2);

	pci_read_config_byte(pdev, NV_MCP_SATA_CFG_20, &regval);
	regval &= ~NV_MCP_SATA_CFG_20_SATA_SPACE_EN;
	pci_write_config_byte(pdev, NV_MCP_SATA_CFG_20, regval);
}

static void nv_check_hotplug_mcp55(struct ata_host_set *host_set)
{
	u8 intr_status,intr_status1;

	intr_status = readb(host_set->mmio_base + NV_INT_STATUS_MCP55);
	intr_status1 = readb(host_set->mmio_base + NV_INT_STATUS_MCP55+2);

	// Clear interrupt status.
	writeb(0xff, host_set->mmio_base + NV_INT_STATUS_MCP55);
	writeb(0xff, host_set->mmio_base + NV_INT_STATUS_MCP55+2);	

	if ((intr_status & 0x0c) || (intr_status1&0x0c)) {
		if (intr_status & 0x04)
			printk(KERN_WARNING "nv_sata: "
				"Primary device added\n");

		if (intr_status & 0x08)
			printk(KERN_WARNING "nv_sata: "
				"Primary device removed\n");

		if (intr_status1 & 0x04)
			printk(KERN_WARNING "nv_sata: "
				"Secondary device added\n");

		if (intr_status1 & 0x08)
			printk(KERN_WARNING "nv_sata: "
				"Secondary device removed\n");
	}
}


static void nv_sgpio_init(struct pci_dev *pdev, struct nv_host *phost)
{
	u16 csr_add; 
	u32 cb_add, temp32;
	struct device *dev = pci_dev_to_dev(pdev);
	struct ata_host_set *host_set = dev_get_drvdata(dev);
	u8 pro=0;
	pci_read_config_word(pdev, NV_SGPIO_PCI_CSR_OFFSET, &csr_add);
	pci_read_config_dword(pdev, NV_SGPIO_PCI_CB_OFFSET, &cb_add);
	pci_read_config_byte(pdev, 0xA4, &pro);
    	
	if (csr_add == 0 || cb_add == 0) 
        	return;
    	

	if (!(pro&0x40))
		return;	
	
	
	temp32 = csr_add;
	phost->host_sgpio.pcsr = (void *)temp32;
	phost->host_sgpio.pcb = phys_to_virt(cb_add);

	if (phost->host_sgpio.pcb->nvcr.bit.init_cnt!=0x2 || phost->host_sgpio.pcb->nvcr.bit.cbver!=0x0)
		return;
		
	if (temp32 <=0x200 || temp32 >=0xFFFE )
		return;
	
	
	if (cb_add<=0x80000 || cb_add>=0x9FC00)
		return;
	
		
	if (phost->host_sgpio.pcb->scratch_space == 0) {
        	spin_lock_init(&nv_sgpio_lock);
        	phost->host_sgpio.share.plock = &nv_sgpio_lock;
        	phost->host_sgpio.share.ptstamp = &nv_sgpio_tstamp;
        	phost->host_sgpio.pcb->scratch_space = 
			(unsigned long)&phost->host_sgpio.share;
        	spin_lock(phost->host_sgpio.share.plock);
        	nv_sgpio_reset(phost->host_sgpio.pcsr);
        	phost->host_sgpio.pcb->cr0 = 
			SET_ENABLE(phost->host_sgpio.pcb->cr0);

		spin_unlock(phost->host_sgpio.share.plock);
    	}

	phost->host_sgpio.share = 
		*(struct nv_sgpio_host_share *)(unsigned long)
	phost->host_sgpio.pcb->scratch_space;
	phost->host_sgpio.flags.sgpio_enabled = 1;
	phost->pdev = pdev;
	init_timer(&phost->host_sgpio.sgpio_timer);
	phost->host_sgpio.sgpio_timer.data = (unsigned long)phost;
	nv_sgpio_set_timer(&phost->host_sgpio.sgpio_timer, 
				NV_SGPIO_UPDATE_TICK);
}

static void __nv_sgpio_timer_handler(unsigned long context);
static void nv_sgpio_set_timer(struct timer_list *ptimer, unsigned int timeout_msec)
{
	if (!ptimer)
		return;
	ptimer->function = __nv_sgpio_timer_handler;
	ptimer->expires = msecs_to_jiffies(timeout_msec) + jiffies;
	add_timer(ptimer);
}
static void __nv_sgpio_timer_handler(unsigned long context)
{
	struct nv_host *phost = (struct nv_host*)context;
	struct device *dev = pci_dev_to_dev(phost->pdev);
	struct ata_host_set *host_set = dev_get_drvdata(dev);
	
	if (!host_set)
		nv_sgpio_set_timer(&phost->host_sgpio.sgpio_timer, 
				NV_SGPIO_UPDATE_TICK);
	else
		nv_sgpio_timer_handler(host_set);
	
}

static void nv_sgpio_timer_handler(unsigned long context)
{

    	struct ata_host_set *host_set = (struct ata_host_set *)context;
    	struct nv_host *host;
    	u8 count, host_offset, port_offset;
    	union nv_sgpio_tx tx;
    	bool on_off;
    	unsigned long mask = 0xFFFF;
	struct nv_port *port;

    	if (!host_set)
		goto err_out;
	else 
		host = (struct nv_host *)host_set->private_data;

	if (!host->host_sgpio.flags.sgpio_enabled)
	        goto err_out;

	host_offset = nv_sgpio_tx_host_offset(host_set);

    	spin_lock(host->host_sgpio.share.plock);
    	tx = host->host_sgpio.pcb->tx[host_offset];
    	spin_unlock(host->host_sgpio.share.plock);

    	for (count = 0; count < host_set->n_ports; count++) {
        	struct ata_port *ap; 

        	ap = host_set->ports[count];
        
        	if (!(ap && !(ap->flags & ATA_FLAG_PORT_DISABLED)))
			continue;

            	port = (struct nv_port *)ap->private_data;
		if (!port)
			continue;            		
                port_offset = nv_sgpio_tx_port_offset(ap);
		on_off = GET_ACTIVITY(tx.tx_port[port_offset]);
		if (nv_sgpio_update_led(&port->port_sgpio.activity, &on_off)) {
			tx.tx_port[port_offset] = 
				SET_ACTIVITY(tx.tx_port[port_offset], on_off);
			host->host_sgpio.flags.need_update = 1;
               }
    	}


	if (host->host_sgpio.flags.need_update) {
		spin_lock(host->host_sgpio.share.plock);    
        	if (nv_sgpio_get_func(host_set) 
			% NV_CNTRLR_SHARE_INIT == 0) {
            		host->host_sgpio.pcb->tx[host_offset].all &= mask;
            		mask = mask << 16;
            		tx.all &= mask;
        	} else {
            		tx.all &= mask;
            		mask = mask << 16;
            		host->host_sgpio.pcb->tx[host_offset].all &= mask;
        	}
        	host->host_sgpio.pcb->tx[host_offset].all |= tx.all;
		spin_unlock(host->host_sgpio.share.plock);     
 
		if (nv_sgpio_send_cmd(host, NV_SGPIO_CMD_WRITE_DATA)) { 
                	host->host_sgpio.flags.need_update = 0;
			return;
		}
    	} else {
    		nv_sgpio_set_timer(&host->host_sgpio.sgpio_timer, 
				NV_SGPIO_UPDATE_TICK);
	}
err_out:
	return;
}

static bool nv_sgpio_send_cmd(struct nv_host *host, u8 cmd)
{
	u8 csr;
	unsigned long *ptstamp;

	spin_lock(host->host_sgpio.share.plock);    
	ptstamp = host->host_sgpio.share.ptstamp;
	if (jiffies_to_msecs1(jiffies - *ptstamp) >= NV_SGPIO_MIN_UPDATE_DELTA) {
		csr = 
		nv_sgpio_get_csr((unsigned long)host->host_sgpio.pcsr);
		if ((GET_SGPIO_STATUS(csr) != NV_SGPIO_STATE_OPERATIONAL) ||
			(GET_CMD_STATUS(csr) == NV_SGPIO_CMD_ACTIVE)) {
			//nv_sgpio_reset(host->host_sgpio.pcsr);
		} else {
			host->host_sgpio.pcb->cr0 = 
				SET_ENABLE(host->host_sgpio.pcb->cr0);
			csr = 0;
			csr = SET_CMD(csr, cmd);
			nv_sgpio_set_csr(csr, 
				(unsigned long)host->host_sgpio.pcsr);
			*ptstamp = jiffies;
		}
		spin_unlock(host->host_sgpio.share.plock);
		nv_sgpio_set_timer(&host->host_sgpio.sgpio_timer, 
			NV_SGPIO_UPDATE_TICK);
        	return 1;
	} else {
		spin_unlock(host->host_sgpio.share.plock);
		nv_sgpio_set_timer(&host->host_sgpio.sgpio_timer, 
				(NV_SGPIO_MIN_UPDATE_DELTA - 
				jiffies_to_msecs1(jiffies - *ptstamp)));
		return 0;
    	}
}

static bool nv_sgpio_update_led(struct nv_sgpio_led *led, bool *on_off)
{
	bool need_update = 0;

	if (led->force_off > 0) {
		led->force_off--;
	} else if (led->flags.recent_activity ^ led->flags.last_state) {
		*on_off = led->flags.recent_activity;
		led->flags.last_state = led->flags.recent_activity;
		need_update = 1;
	} else if ((led->flags.recent_activity & led->flags.last_state) &&
		(led->last_cons_active >= NV_SGPIO_MAX_ACTIVITY_ON)) {
		*on_off = NV_OFF;
		led->flags.last_state = NV_OFF;
		led->force_off = NV_SGPIO_MIN_FORCE_OFF;
		need_update = 1;
	}

	if (*on_off) 
		led->last_cons_active++;	
	else
		led->last_cons_active = 0;

	led->flags.recent_activity = 0;
	return need_update;
}

static void nv_sgpio_reset(u8  *pcsr)
{
	u8 csr;

	csr = nv_sgpio_get_csr((unsigned long)pcsr);
	if (GET_SGPIO_STATUS(csr) == NV_SGPIO_STATE_RESET) {
		csr = 0;
		csr = SET_CMD(csr, NV_SGPIO_CMD_RESET);
		nv_sgpio_set_csr(csr, (unsigned long)pcsr);
	}
	csr = 0;
	csr = SET_CMD(csr, NV_SGPIO_CMD_READ_PARAMS);
	nv_sgpio_set_csr(csr, (unsigned long)pcsr);
}

static void nv_sgpio_host_cleanup(struct nv_host *host)
{
	u8 csr;
	if (!host)
		return;

	if (host->host_sgpio.flags.sgpio_enabled){
		spin_lock(host->host_sgpio.share.plock);
		host->host_sgpio.pcb->cr0 = 
			SET_ENABLE(host->host_sgpio.pcb->cr0);
		csr = 0;
		csr = SET_CMD(csr, NV_SGPIO_CMD_WRITE_DATA);
		nv_sgpio_set_csr(csr, 
			(unsigned long)host->host_sgpio.pcsr);
		spin_unlock(host->host_sgpio.share.plock);
	
		if (timer_pending(&host->host_sgpio.sgpio_timer))
			del_timer(&host->host_sgpio.sgpio_timer);
		host->host_sgpio.flags.sgpio_enabled = 0;
		host->host_sgpio.pcb->scratch_space = 0;
	}
	
}

static void nv_sgpio_clear_all_leds(struct ata_port *ap)
{
	struct nv_port *port = ap->private_data;
	struct nv_host *host;
	u8 host_offset, port_offset;

	if (!port || !ap->host_set)
		return;
	if (!ap->host_set->private_data)
		return;

	host = ap->host_set->private_data;
	if (!host->host_sgpio.flags.sgpio_enabled)
		return;

	host_offset = nv_sgpio_tx_host_offset(ap->host_set);
	port_offset = nv_sgpio_tx_port_offset(ap);

	spin_lock(host->host_sgpio.share.plock);
	host->host_sgpio.pcb->tx[host_offset].tx_port[port_offset] = 0;
	host->host_sgpio.flags.need_update = 1;
	spin_unlock(host->host_sgpio.share.plock);
}



static int __init nv_init(void)
{
#ifdef RHAS3U7
	int rc;
	rc = pci_module_init(&nv_pci_driver);
	if (rc)
		return rc;
	
	rc = scsi_register_module(MODULE_SCSI_HA, &nv_sht);
	if (rc) {
		pci_unregister_driver(&nv_pci_driver);
		/* TODO: does scsi_register_module return errno val? */
		return -ENODEV;
	}

	return 0;
#else
	return pci_module_init(&nv_pci_driver);
#endif
}

static void __exit nv_exit(void)
{
#ifdef RHAS3U7
	scsi_unregister_module(MODULE_SCSI_HA, &nv_sht);
#endif
	pci_unregister_driver(&nv_pci_driver);

}

module_init(nv_init);
module_exit(nv_exit);
