/*
 *  sata_nv.c - NVIDIA nForce SATA
 *
 *  Copyright 2004 NVIDIA Corp.  All rights reserved.
 *  Copyright 2004 Andrew Chew
 *
 *
 *  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.
 *
 *
 *  libata documentation is available via 'make {ps|pdf}docs',
 *  as Documentation/DocBook/libata.*
 *
 *  No hardware documentation available outside of NVIDIA.
 *  This driver programs the NVIDIA SATA controller in a similar
 *  fashion as with other PCI IDE BMDMA controllers, with a few
 *  NV-specific details such as register offsets, SATA phy location,
 *  hotplug info, etc.
 *
 */

#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 <linux/device.h>
#include <scsi/scsi_host.h>
#include <linux/libata.h>

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

enum {
	NV_PORTS			= 2,
	NV_PIO_MASK			= 0x1f,
	NV_MWDMA_MASK			= 0x07,
	NV_UDMA_MASK			= 0x7f,
	NV_PORT0_SCR_REG_OFFSET		= 0x00,
	NV_PORT1_SCR_REG_OFFSET		= 0x40,

	/* INT_STATUS/ENABLE */
	NV_INT_STATUS			= 0x10,
	NV_INT_ENABLE			= 0x11,
	NV_INT_STATUS_CK804		= 0x440,
	NV_INT_ENABLE_CK804		= 0x441,

	/* INT_STATUS/ENABLE bits */
	NV_INT_DEV			= 0x01,
	NV_INT_PM			= 0x02,
	NV_INT_ADDED			= 0x04,
	NV_INT_REMOVED			= 0x08,

	NV_INT_PORT_SHIFT		= 4,	/* each port occupies 4 bits */

	NV_INT_ALL			= 0x0f,
	NV_INT_MASK			= NV_INT_DEV |
					  NV_INT_ADDED | NV_INT_REMOVED,

	/* INT_CONFIG */
	NV_INT_CONFIG			= 0x12,
	NV_INT_CONFIG_METHD		= 0x01, // 0 = INT, 1 = SMI

	// For PCI config register 20
	NV_MCP_SATA_CFG_20		= 0x50,
	NV_MCP_SATA_CFG_20_SATA_SPACE_EN = 0x04,
};

//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


#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 *host)
{
	u8 devfn = (to_pci_dev(host->dev))->devfn;
	return (PCI_FUNC(devfn));
}

static inline u8 nv_sgpio_tx_host_offset(struct ata_host *host)
{
	return (nv_sgpio_get_func(host)/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);
	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 void nv_ck804_host_stop(struct ata_host *host);
static irqreturn_t nv_generic_interrupt(int irq, void *dev_instance,
					struct pt_regs *regs);
static irqreturn_t nv_nf2_interrupt(int irq, void *dev_instance,
				    struct pt_regs *regs);
static irqreturn_t nv_ck804_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 *host);
static int nv_port_start(struct ata_port *ap);
static void nv_port_stop(struct ata_port *ap);
static unsigned int nv_qc_issue(struct ata_queued_cmd *qc);
static void nv_nf2_freeze(struct ata_port *ap);
static void nv_nf2_thaw(struct ata_port *ap);
static void nv_ck804_freeze(struct ata_port *ap);
static void nv_ck804_thaw(struct ata_port *ap);
static void nv_error_handler(struct ata_port *ap);

enum nv_host_type
{
	GENERIC,
	NFORCE2,
	NFORCE3 = NFORCE2,	/* NF2 == NF3 as far as sata_nv is concerned */
	CK804
};

static const 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_MCP51_SATA,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, GENERIC },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP51_SATA2,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, GENERIC },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SATA,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, GENERIC },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP55_SATA2,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, GENERIC },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP61_SATA,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, GENERIC },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP61_SATA2,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, GENERIC },
	{ PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NFORCE_MCP61_SATA3,
		PCI_ANY_ID, PCI_ANY_ID, 0, 0, GENERIC },
	{ PCI_VENDOR_ID_NVIDIA, 0x045c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, GENERIC },
	{ PCI_VENDOR_ID_NVIDIA, 0x045d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, GENERIC },
	{ PCI_VENDOR_ID_NVIDIA, 0x045e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, GENERIC },
	{ PCI_VENDOR_ID_NVIDIA, 0x045f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, GENERIC },
	{ PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID,
		PCI_ANY_ID, PCI_ANY_ID,
		PCI_CLASS_STORAGE_IDE<<8, 0xffff00, GENERIC },
	{ PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID,
		PCI_ANY_ID, PCI_ANY_ID,
		PCI_CLASS_STORAGE_RAID<<8, 0xffff00, GENERIC },
	{ 0, } /* terminate list */
};

struct nv_host
{
	unsigned long		host_flags;
	struct nv_host_sgpio	host_sgpio;
};

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,
};

static struct scsi_host_template nv_sht = {
	.module			= THIS_MODULE,
	.name			= DRV_NAME,
	.ioctl			= ata_scsi_ioctl,
	.queuecommand		= ata_scsi_queuecmd,
	.can_queue		= ATA_DEF_QUEUE,
	.this_id		= ATA_SHT_THIS_ID,
	.sg_tablesize		= LIBATA_MAX_PRD,
	.cmd_per_lun		= ATA_SHT_CMD_PER_LUN,
	.emulated		= ATA_SHT_EMULATED,
	.use_clustering		= ATA_SHT_USE_CLUSTERING,
	.proc_name		= DRV_NAME,
	.dma_boundary		= ATA_DMA_BOUNDARY,
	.slave_configure	= ata_scsi_slave_config,
	.slave_destroy		= ata_scsi_slave_destroy,
	.bios_param		= ata_std_bios_param,
};

static const struct ata_port_operations nv_generic_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,
	.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,
	.freeze			= ata_bmdma_freeze,
	.thaw			= ata_bmdma_thaw,
	.error_handler		= nv_error_handler,
	.post_internal_cmd	= ata_bmdma_post_internal_cmd,
	.data_xfer		= ata_pio_data_xfer,
	.irq_handler		= nv_generic_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,
};

static const struct ata_port_operations nv_nf2_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,
	.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,
	.freeze			= nv_nf2_freeze,
	.thaw			= nv_nf2_thaw,
	.error_handler		= nv_error_handler,
	.post_internal_cmd	= ata_bmdma_post_internal_cmd,
	.data_xfer		= ata_pio_data_xfer,
	.irq_handler		= nv_nf2_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,
};

static const struct ata_port_operations nv_ck804_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,
	.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,
	.freeze			= nv_ck804_freeze,
	.thaw			= nv_ck804_thaw,
	.error_handler		= nv_error_handler,
	.post_internal_cmd	= ata_bmdma_post_internal_cmd,
	.data_xfer		= ata_pio_data_xfer,
	.irq_handler		= nv_ck804_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_ck804_host_stop,
};

static struct ata_port_info nv_port_info[] = {
	/* generic */
	{
		.sht		= &nv_sht,
		.flags	        = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY,
		.pio_mask	= NV_PIO_MASK,
		.mwdma_mask	= NV_MWDMA_MASK,
		.udma_mask	= NV_UDMA_MASK,
		.port_ops	= &nv_generic_ops,
	},
	/* nforce2/3 */
	{
		.sht		= &nv_sht,
		.flags	        = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY,
		.pio_mask	= NV_PIO_MASK,
		.mwdma_mask	= NV_MWDMA_MASK,
		.udma_mask	= NV_UDMA_MASK,
		.port_ops	= &nv_nf2_ops,
	},
	/* ck804 */
	{
		.sht		= &nv_sht,
		.flags	        = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY,
		.pio_mask	= NV_PIO_MASK,
		.mwdma_mask	= NV_MWDMA_MASK,
		.udma_mask	= NV_UDMA_MASK,
		.port_ops	= &nv_ck804_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_generic_interrupt(int irq, void *dev_instance,
					struct pt_regs *regs)
{
	struct ata_host *host = dev_instance;
	unsigned int i;
	unsigned int handled = 0;
	unsigned long flags;

	spin_lock_irqsave(&host->lock, flags);

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

		ap = host->ports[i];
		if (ap &&
		    !(ap->flags & ATA_FLAG_DISABLED)) {
			struct ata_queued_cmd *qc;

			qc = ata_qc_from_tag(ap, ap->active_tag);
			if (qc && (!(qc->tf.flags & ATA_TFLAG_POLLING)))
				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);
		}

	}

	spin_unlock_irqrestore(&host->lock, flags);

	return IRQ_RETVAL(handled);
}

static int nv_host_intr(struct ata_port *ap, u8 irq_stat)
{
	struct ata_queued_cmd *qc = ata_qc_from_tag(ap, ap->active_tag);
	int handled;

	/* freeze if hotplugged */
	if (unlikely(irq_stat & (NV_INT_ADDED | NV_INT_REMOVED))) {
		ata_port_freeze(ap);
		return 1;
	}

	/* bail out if not our interrupt */
	if (!(irq_stat & NV_INT_DEV))
		return 0;

	/* DEV interrupt w/ no active qc? */
	if (unlikely(!qc || (qc->tf.flags & ATA_TFLAG_POLLING))) {
		ata_check_status(ap);
		return 1;
	}

	/* handle interrupt */
	handled = ata_host_intr(ap, qc);
	if (unlikely(!handled)) {
		/* spurious, clear it */
		ata_check_status(ap);
	}

	return 1;
}

static irqreturn_t nv_do_interrupt(struct ata_host *host, u8 irq_stat)
{
	int i, handled = 0;

	for (i = 0; i < host->n_ports; i++) {
		struct ata_port *ap = host->ports[i];

		if (ap && !(ap->flags & ATA_FLAG_DISABLED))
			handled += nv_host_intr(ap, irq_stat);

		irq_stat >>= NV_INT_PORT_SHIFT;
	}

	return IRQ_RETVAL(handled);
}

static irqreturn_t nv_nf2_interrupt(int irq, void *dev_instance,
				    struct pt_regs *regs)
{
	struct ata_host *host = dev_instance;
	u8 irq_stat;
	irqreturn_t ret;

	spin_lock(&host->lock);
	irq_stat = inb(host->ports[0]->ioaddr.scr_addr + NV_INT_STATUS);
	ret = nv_do_interrupt(host, irq_stat);
	spin_unlock(&host->lock);

	return ret;
}

static irqreturn_t nv_ck804_interrupt(int irq, void *dev_instance,
				      struct pt_regs *regs)
{
	struct ata_host *host = dev_instance;
	u8 irq_stat;
	irqreturn_t ret;

	spin_lock(&host->lock);
	irq_stat = readb(host->mmio_base + NV_INT_STATUS_CK804);
	ret = nv_do_interrupt(host, irq_stat);
	spin_unlock(&host->lock);

	return ret;
}

static u32 nv_scr_read (struct ata_port *ap, unsigned int sc_reg)
{
	if (sc_reg > SCR_CONTROL)
		return 0xffffffffU;

	return ioread32((void __iomem *)ap->ioaddr.scr_addr + (sc_reg * 4));
}

static void nv_scr_write (struct ata_port *ap, unsigned int sc_reg, u32 val)
{
	if (sc_reg > SCR_CONTROL)
		return;

	iowrite32(val, (void __iomem *)ap->ioaddr.scr_addr + (sc_reg * 4));
}

static void nv_nf2_freeze(struct ata_port *ap)
{
	unsigned long scr_addr = ap->host->ports[0]->ioaddr.scr_addr;
	int shift = ap->port_no * NV_INT_PORT_SHIFT;
	u8 mask;

	mask = inb(scr_addr + NV_INT_ENABLE);
	mask &= ~(NV_INT_ALL << shift);
	outb(mask, scr_addr + NV_INT_ENABLE);
}

static void nv_nf2_thaw(struct ata_port *ap)
{
	unsigned long scr_addr = ap->host->ports[0]->ioaddr.scr_addr;
	int shift = ap->port_no * NV_INT_PORT_SHIFT;
	u8 mask;

	outb(NV_INT_ALL << shift, scr_addr + NV_INT_STATUS);

	mask = inb(scr_addr + NV_INT_ENABLE);
	mask |= (NV_INT_MASK << shift);
	outb(mask, scr_addr + NV_INT_ENABLE);
}

static void nv_ck804_freeze(struct ata_port *ap)
{
	void __iomem *mmio_base = ap->host->mmio_base;
	int shift = ap->port_no * NV_INT_PORT_SHIFT;
	u8 mask;

	mask = readb(mmio_base + NV_INT_ENABLE_CK804);
	mask &= ~(NV_INT_ALL << shift);
	writeb(mask, mmio_base + NV_INT_ENABLE_CK804);
}

static void nv_ck804_thaw(struct ata_port *ap)
{
	void __iomem *mmio_base = ap->host->mmio_base;
	int shift = ap->port_no * NV_INT_PORT_SHIFT;
	u8 mask;

	writeb(NV_INT_ALL << shift, mmio_base + NV_INT_STATUS_CK804);

	mask = readb(mmio_base + NV_INT_ENABLE_CK804);
	mask |= (NV_INT_MASK << shift);
	writeb(mask, mmio_base + NV_INT_ENABLE_CK804);
}

static int nv_hardreset(struct ata_port *ap, unsigned int *class)
{
	unsigned int dummy;

	/* SATA hardreset fails to retrieve proper device signature on
	 * some controllers.  Don't classify on hardreset.  For more
	 * info, see http://bugme.osdl.org/show_bug.cgi?id=3352
	 */
	return sata_std_hardreset(ap, &dummy);
}

static void nv_error_handler(struct ata_port *ap)
{
	ata_bmdma_drive_eh(ap, ata_std_prereset, ata_std_softreset,
			   nv_hardreset, ata_std_postreset);
}
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[2];
	struct ata_probe_ent *probe_ent;
	int pci_dev_busy = 0;
	int rc;
	u32 bar;
	unsigned long base;

        // 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++)
		dev_printk(KERN_DEBUG, &pdev->dev, "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;
	rc = pci_set_consistent_dma_mask(pdev, ATA_DMA_MASK);
	if (rc)
		goto err_out_regions;

	rc = -ENOMEM;

 	ppi[0] = ppi[1] = &nv_port_info[ent->driver_data];
	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));

	probe_ent->private_data = host;
	probe_ent->mmio_base = pci_iomap(pdev, 5, 0);
	if (!probe_ent->mmio_base) {
		rc = -EIO;
		goto err_out_free_ent;
	}

	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;

	/* enable SATA space for CK804 */
	if (ent->driver_data == CK804) {
		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);
	}

	pci_set_master(pdev);

	rc = ata_device_add(probe_ent);
	if (rc != NV_PORTS)
		goto err_out_iounmap;
	if (nv_sgpio_capable(ent))
		nv_sgpio_init(pdev, host);

	kfree(probe_ent);

	return 0;

err_out_iounmap:
	pci_iounmap(pdev, probe_ent->mmio_base);
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 unsigned 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_ck804_host_stop(struct ata_host *host)
{
	struct nv_host *nv_host = host->private_data;
	struct pci_dev *pdev = to_pci_dev(host->dev);
	u8 regval;

	/* disable SATA space for CK804 */
	nv_sgpio_host_cleanup(nv_host);
	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);

	ata_pci_host_stop(host);
}

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


	nv_sgpio_host_cleanup(nv_host);
	kfree(nv_host);
	ata_pci_host_stop(host);

}

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 *host = 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;

	init_timer(&phost->host_sgpio.sgpio_timer);
	phost->host_sgpio.sgpio_timer.data = (unsigned long)host;
	nv_sgpio_set_timer(&phost->host_sgpio.sgpio_timer, 
				NV_SGPIO_UPDATE_TICK);
}

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 ata_host *host_set = (struct ata_host *)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_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_msecs(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)) {
			
		} 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_msecs(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)
		return;
	if (!ap->host->private_data)
		return;

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

	host_offset = nv_sgpio_tx_host_offset(ap->host);
	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)
{
	return pci_register_driver(&nv_pci_driver);
}

static void __exit nv_exit(void)
{
	pci_unregister_driver(&nv_pci_driver);
}

module_init(nv_init);
module_exit(nv_exit);
