/*
* HighPoint RR3xxx controller driver for Linux
* Copyright (C) 2006 HighPoint Technologies, Inc. All Rights Reserved.
*
* 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; version 2 of the License.
*
* 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.
*
* Please report bugs/comments/suggestions to linux@highpoint-tech.com
*
* For more information, visit http://www.highpoint-tech.com
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/timer.h>
#include <linux/spinlock.h>
#include <linux/hdreg.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/div64.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi.h>
#include <scsi/scsi_tcq.h>
#include <scsi/scsi_host.h>
#include "hptiop.h"
MODULE_AUTHOR("HighPoint Technologies, Inc.");
MODULE_DESCRIPTION("HighPoint RocketRAID 3xxx SATA Controller Driver");
static char driver_name[] = "hptiop";
static const char driver_name_long[] = "RocketRAID 3xxx SATA Controller driver";
static const char driver_ver[] = "v1.0 (060426)";
static void hptiop_host_request_callback(struct hptiop_hba *hba, u32 tag);
static void hptiop_iop_request_callback(struct hptiop_hba *hba, u32 tag);
static void hptiop_message_callback(struct hptiop_hba *hba, u32 msg);
static inline void hptiop_pci_posting_flush(struct hpt_iopmu __iomem *iop)
{
readl(&iop->outbound_intstatus);
}
static int iop_wait_ready(struct hpt_iopmu __iomem *iop, u32 millisec)
{
u32 req = 0;
int i;
for (i = 0; i < millisec; i++) {
req = readl(&iop->inbound_queue);
if (req != IOPMU_QUEUE_EMPTY)
break;
msleep(1);
}
if (req != IOPMU_QUEUE_EMPTY) {
writel(req, &iop->outbound_queue);
hptiop_pci_posting_flush(iop);
return 0;
}
return -1;
}
static void hptiop_request_callback(struct hptiop_hba *hba, u32 tag)
{
if ((tag & IOPMU_QUEUE_MASK_HOST_BITS) == IOPMU_QUEUE_ADDR_HOST_BIT)
return hptiop_host_request_callback(hba,
tag & ~IOPMU_QUEUE_ADDR_HOST_BIT);
else
return hptiop_iop_request_callback(hba, tag);
}
static inline void hptiop_drain_outbound_queue(struct hptiop_hba *hba)
{
u32 req;
while ((req = readl(&hba->iop->outbound_queue)) != IOPMU_QUEUE_EMPTY) {
if (req & IOPMU_QUEUE_MASK_HOST_BITS)
hptiop_request_callback(hba, req);
else {
struct hpt_iop_request_header __iomem * p;
p = (struct hpt_iop_request_header __iomem *)
((char __iomem *)hba->iop + req);
if (readl(&p->flags) & IOP_REQUEST_FLAG_SYNC_REQUEST) {
if (readl(&p->context))
hptiop_request_callback(hba, req);
else
writel(1, &p->context);
}
else
hptiop_request_callback(hba, req);
}
}
}
static int __iop_intr(struct hptiop_hba *hba)
{
struct hpt_iopmu __iomem *iop = hba->iop;
u32 status;
int ret = 0;
status = readl(&iop->outbound_intstatus);
if (status & IOPMU_OUTBOUND_INT_MSG0) {
u32 msg = readl(&iop->outbound_msgaddr0);
dprintk("received outbound msg %x\n", msg);
writel(IOPMU_OUTBOUND_INT_MSG0, &iop->outbound_intstatus);
hptiop_message_callback(hba, msg);
ret = 1;
}
if (status & IOPMU_OUTBOUND_INT_POSTQUEUE) {
hptiop_drain_outbound_queue(hba);
ret = 1;
}
return ret;
}
static int iop_send_sync_request(struct hptiop_hba *hba,
void __iomem *_req, u32 millisec)
{
struct hpt_iop_request_header __iomem *req = _req;
u32 i;
writel(readl(&req->flags) | IOP_REQUEST_FLAG_SYNC_REQUEST,
&req->flags);
writel(0, &req->context);
writel((unsigned long)req - (unsigned long)hba->iop,
&hba->iop->inbound_queue);
hptiop_pci_posting_flush(hba->iop);
for (i = 0; i < millisec; i++) {
__iop_intr(hba);
if (readl(&req->context))
return 0;
msleep(1);
}
return -1;
}
static int