162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
362306a36Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
462306a36Sopenharmony_ci * for more details.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 1996 David S. Miller (davem@davemloft.net)
762306a36Sopenharmony_ci * Copyright (C) 1999 Andrew R. Baker (andrewb@uab.edu)
862306a36Sopenharmony_ci * Copyright (C) 2001 Florian Lohoff (flo@rfc822.org)
962306a36Sopenharmony_ci * Copyright (C) 2003, 07 Ralf Baechle (ralf@linux-mips.org)
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * (In all truth, Jed Schimmel wrote all this code.)
1262306a36Sopenharmony_ci */
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#undef DEBUG
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <linux/delay.h>
1762306a36Sopenharmony_ci#include <linux/dma-mapping.h>
1862306a36Sopenharmony_ci#include <linux/gfp.h>
1962306a36Sopenharmony_ci#include <linux/interrupt.h>
2062306a36Sopenharmony_ci#include <linux/init.h>
2162306a36Sopenharmony_ci#include <linux/kernel.h>
2262306a36Sopenharmony_ci#include <linux/types.h>
2362306a36Sopenharmony_ci#include <linux/module.h>
2462306a36Sopenharmony_ci#include <linux/platform_device.h>
2562306a36Sopenharmony_ci#include <linux/spinlock.h>
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#include <asm/sgi/hpc3.h>
2862306a36Sopenharmony_ci#include <asm/sgi/ip22.h>
2962306a36Sopenharmony_ci#include <asm/sgi/wd.h>
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci#include <scsi/scsi.h>
3262306a36Sopenharmony_ci#include <scsi/scsi_cmnd.h>
3362306a36Sopenharmony_ci#include <scsi/scsi_device.h>
3462306a36Sopenharmony_ci#include <scsi/scsi_eh.h>
3562306a36Sopenharmony_ci#include <scsi/scsi_tcq.h>
3662306a36Sopenharmony_ci#include "wd33c93.h"
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistruct ip22_hostdata {
3962306a36Sopenharmony_ci	struct WD33C93_hostdata wh;
4062306a36Sopenharmony_ci	dma_addr_t dma;
4162306a36Sopenharmony_ci	void *cpu;
4262306a36Sopenharmony_ci	struct device *dev;
4362306a36Sopenharmony_ci};
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci#define host_to_hostdata(host) ((struct ip22_hostdata *)((host)->hostdata))
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistruct hpc_chunk {
4862306a36Sopenharmony_ci	struct hpc_dma_desc desc;
4962306a36Sopenharmony_ci	u32 _padding;	/* align to quadword boundary */
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci/* space for hpc dma descriptors */
5362306a36Sopenharmony_ci#define HPC_DMA_SIZE   PAGE_SIZE
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci#define DMA_DIR(d)   ((d == DATA_OUT_DIR) ? DMA_TO_DEVICE : DMA_FROM_DEVICE)
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic irqreturn_t sgiwd93_intr(int irq, void *dev_id)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	struct Scsi_Host * host = dev_id;
6062306a36Sopenharmony_ci	unsigned long flags;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	spin_lock_irqsave(host->host_lock, flags);
6362306a36Sopenharmony_ci	wd33c93_intr(host);
6462306a36Sopenharmony_ci	spin_unlock_irqrestore(host->host_lock, flags);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	return IRQ_HANDLED;
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_cistatic inline
7062306a36Sopenharmony_civoid fill_hpc_entries(struct ip22_hostdata *hd, struct scsi_cmnd *cmd, int din)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	struct scsi_pointer *scsi_pointer = WD33C93_scsi_pointer(cmd);
7362306a36Sopenharmony_ci	unsigned long len = scsi_pointer->this_residual;
7462306a36Sopenharmony_ci	void *addr = scsi_pointer->ptr;
7562306a36Sopenharmony_ci	dma_addr_t physaddr;
7662306a36Sopenharmony_ci	unsigned long count;
7762306a36Sopenharmony_ci	struct hpc_chunk *hcp;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	physaddr = dma_map_single(hd->dev, addr, len, DMA_DIR(din));
8062306a36Sopenharmony_ci	scsi_pointer->dma_handle = physaddr;
8162306a36Sopenharmony_ci	hcp = hd->cpu;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	while (len) {
8462306a36Sopenharmony_ci		/*
8562306a36Sopenharmony_ci		 * even cntinfo could be up to 16383, without
8662306a36Sopenharmony_ci		 * magic only 8192 works correctly
8762306a36Sopenharmony_ci		 */
8862306a36Sopenharmony_ci		count = len > 8192 ? 8192 : len;
8962306a36Sopenharmony_ci		hcp->desc.pbuf = physaddr;
9062306a36Sopenharmony_ci		hcp->desc.cntinfo = count;
9162306a36Sopenharmony_ci		hcp++;
9262306a36Sopenharmony_ci		len -= count;
9362306a36Sopenharmony_ci		physaddr += count;
9462306a36Sopenharmony_ci	}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	/*
9762306a36Sopenharmony_ci	 * To make sure, if we trip an HPC bug, that we transfer every single
9862306a36Sopenharmony_ci	 * byte, we tag on an extra zero length dma descriptor at the end of
9962306a36Sopenharmony_ci	 * the chain.
10062306a36Sopenharmony_ci	 */
10162306a36Sopenharmony_ci	hcp->desc.pbuf = 0;
10262306a36Sopenharmony_ci	hcp->desc.cntinfo = HPCDMA_EOX;
10362306a36Sopenharmony_ci	dma_sync_single_for_device(hd->dev, hd->dma,
10462306a36Sopenharmony_ci		       (unsigned long)(hcp + 1) - (unsigned long)hd->cpu,
10562306a36Sopenharmony_ci		       DMA_TO_DEVICE);
10662306a36Sopenharmony_ci}
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_cistatic int dma_setup(struct scsi_cmnd *cmd, int datainp)
10962306a36Sopenharmony_ci{
11062306a36Sopenharmony_ci	struct scsi_pointer *scsi_pointer = WD33C93_scsi_pointer(cmd);
11162306a36Sopenharmony_ci	struct ip22_hostdata *hdata = host_to_hostdata(cmd->device->host);
11262306a36Sopenharmony_ci	struct hpc3_scsiregs *hregs =
11362306a36Sopenharmony_ci		(struct hpc3_scsiregs *) cmd->device->host->base;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	pr_debug("dma_setup: datainp<%d> hcp<%p> ", datainp, hdata->cpu);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	hdata->wh.dma_dir = datainp;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	/*
12062306a36Sopenharmony_ci	 * wd33c93 shouldn't pass us bogus dma_setups, but it does:-(  The
12162306a36Sopenharmony_ci	 * other wd33c93 drivers deal with it the same way (which isn't that
12262306a36Sopenharmony_ci	 * obvious).  IMHO a better fix would be, not to do these dma setups
12362306a36Sopenharmony_ci	 * in the first place.
12462306a36Sopenharmony_ci	 */
12562306a36Sopenharmony_ci	if (scsi_pointer->ptr == NULL || scsi_pointer->this_residual == 0)
12662306a36Sopenharmony_ci		return 1;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	fill_hpc_entries(hdata, cmd, datainp);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	pr_debug(" HPCGO\n");
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	/* Start up the HPC. */
13362306a36Sopenharmony_ci	hregs->ndptr = hdata->dma;
13462306a36Sopenharmony_ci	if (datainp)
13562306a36Sopenharmony_ci		hregs->ctrl = HPC3_SCTRL_ACTIVE;
13662306a36Sopenharmony_ci	else
13762306a36Sopenharmony_ci		hregs->ctrl = HPC3_SCTRL_ACTIVE | HPC3_SCTRL_DIR;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	return 0;
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic void dma_stop(struct Scsi_Host *instance, struct scsi_cmnd *SCpnt,
14362306a36Sopenharmony_ci		     int status)
14462306a36Sopenharmony_ci{
14562306a36Sopenharmony_ci	struct scsi_pointer *scsi_pointer = WD33C93_scsi_pointer(SCpnt);
14662306a36Sopenharmony_ci	struct ip22_hostdata *hdata = host_to_hostdata(instance);
14762306a36Sopenharmony_ci	struct hpc3_scsiregs *hregs;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	if (!SCpnt)
15062306a36Sopenharmony_ci		return;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if (scsi_pointer->ptr == NULL || scsi_pointer->this_residual == 0)
15362306a36Sopenharmony_ci		return;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	hregs = (struct hpc3_scsiregs *) SCpnt->device->host->base;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	pr_debug("dma_stop: status<%d> ", status);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	/* First stop the HPC and flush it's FIFO. */
16062306a36Sopenharmony_ci	if (hdata->wh.dma_dir) {
16162306a36Sopenharmony_ci		hregs->ctrl |= HPC3_SCTRL_FLUSH;
16262306a36Sopenharmony_ci		while (hregs->ctrl & HPC3_SCTRL_ACTIVE)
16362306a36Sopenharmony_ci			barrier();
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci	hregs->ctrl = 0;
16662306a36Sopenharmony_ci	dma_unmap_single(hdata->dev, scsi_pointer->dma_handle,
16762306a36Sopenharmony_ci			 scsi_pointer->this_residual,
16862306a36Sopenharmony_ci			 DMA_DIR(hdata->wh.dma_dir));
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	pr_debug("\n");
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_civoid sgiwd93_reset(unsigned long base)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	struct hpc3_scsiregs *hregs = (struct hpc3_scsiregs *) base;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci	hregs->ctrl = HPC3_SCTRL_CRESET;
17862306a36Sopenharmony_ci	udelay(50);
17962306a36Sopenharmony_ci	hregs->ctrl = 0;
18062306a36Sopenharmony_ci}
18162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(sgiwd93_reset);
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic inline void init_hpc_chain(struct ip22_hostdata *hdata)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	struct hpc_chunk *hcp = (struct hpc_chunk *)hdata->cpu;
18662306a36Sopenharmony_ci	dma_addr_t dma = hdata->dma;
18762306a36Sopenharmony_ci	unsigned long start, end;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci	start = (unsigned long) hcp;
19062306a36Sopenharmony_ci	end = start + HPC_DMA_SIZE;
19162306a36Sopenharmony_ci	while (start < end) {
19262306a36Sopenharmony_ci		hcp->desc.pnext = (u32) (dma + sizeof(struct hpc_chunk));
19362306a36Sopenharmony_ci		hcp->desc.cntinfo = HPCDMA_EOX;
19462306a36Sopenharmony_ci		hcp++;
19562306a36Sopenharmony_ci		dma += sizeof(struct hpc_chunk);
19662306a36Sopenharmony_ci		start += sizeof(struct hpc_chunk);
19762306a36Sopenharmony_ci	}
19862306a36Sopenharmony_ci	hcp--;
19962306a36Sopenharmony_ci	hcp->desc.pnext = hdata->dma;
20062306a36Sopenharmony_ci}
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci/*
20362306a36Sopenharmony_ci * Kludge alert - the SCSI code calls the abort and reset method with int
20462306a36Sopenharmony_ci * arguments not with pointers.  So this is going to blow up beautyfully
20562306a36Sopenharmony_ci * on 64-bit systems with memory outside the compat address spaces.
20662306a36Sopenharmony_ci */
20762306a36Sopenharmony_cistatic const struct scsi_host_template sgiwd93_template = {
20862306a36Sopenharmony_ci	.module			= THIS_MODULE,
20962306a36Sopenharmony_ci	.proc_name		= "SGIWD93",
21062306a36Sopenharmony_ci	.name			= "SGI WD93",
21162306a36Sopenharmony_ci	.queuecommand		= wd33c93_queuecommand,
21262306a36Sopenharmony_ci	.eh_abort_handler	= wd33c93_abort,
21362306a36Sopenharmony_ci	.eh_host_reset_handler	= wd33c93_host_reset,
21462306a36Sopenharmony_ci	.can_queue		= 16,
21562306a36Sopenharmony_ci	.this_id		= 7,
21662306a36Sopenharmony_ci	.sg_tablesize		= SG_ALL,
21762306a36Sopenharmony_ci	.cmd_per_lun		= 8,
21862306a36Sopenharmony_ci	.dma_boundary		= PAGE_SIZE - 1,
21962306a36Sopenharmony_ci	.cmd_size		= sizeof(struct scsi_pointer),
22062306a36Sopenharmony_ci};
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_cistatic int sgiwd93_probe(struct platform_device *pdev)
22362306a36Sopenharmony_ci{
22462306a36Sopenharmony_ci	struct sgiwd93_platform_data *pd = pdev->dev.platform_data;
22562306a36Sopenharmony_ci	unsigned char *wdregs = pd->wdregs;
22662306a36Sopenharmony_ci	struct hpc3_scsiregs *hregs = pd->hregs;
22762306a36Sopenharmony_ci	struct ip22_hostdata *hdata;
22862306a36Sopenharmony_ci	struct Scsi_Host *host;
22962306a36Sopenharmony_ci	wd33c93_regs regs;
23062306a36Sopenharmony_ci	unsigned int unit = pd->unit;
23162306a36Sopenharmony_ci	unsigned int irq = pd->irq;
23262306a36Sopenharmony_ci	int err;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	host = scsi_host_alloc(&sgiwd93_template, sizeof(struct ip22_hostdata));
23562306a36Sopenharmony_ci	if (!host) {
23662306a36Sopenharmony_ci		err = -ENOMEM;
23762306a36Sopenharmony_ci		goto out;
23862306a36Sopenharmony_ci	}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	host->base = (unsigned long) hregs;
24162306a36Sopenharmony_ci	host->irq = irq;
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	hdata = host_to_hostdata(host);
24462306a36Sopenharmony_ci	hdata->dev = &pdev->dev;
24562306a36Sopenharmony_ci	hdata->cpu = dma_alloc_noncoherent(&pdev->dev, HPC_DMA_SIZE,
24662306a36Sopenharmony_ci				&hdata->dma, DMA_TO_DEVICE, GFP_KERNEL);
24762306a36Sopenharmony_ci	if (!hdata->cpu) {
24862306a36Sopenharmony_ci		printk(KERN_WARNING "sgiwd93: Could not allocate memory for "
24962306a36Sopenharmony_ci		       "host %d buffer.\n", unit);
25062306a36Sopenharmony_ci		err = -ENOMEM;
25162306a36Sopenharmony_ci		goto out_put;
25262306a36Sopenharmony_ci	}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	init_hpc_chain(hdata);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	regs.SASR = wdregs + 3;
25762306a36Sopenharmony_ci	regs.SCMD = wdregs + 7;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	hdata->wh.no_sync = 0;
26062306a36Sopenharmony_ci	hdata->wh.fast = 1;
26162306a36Sopenharmony_ci	hdata->wh.dma_mode = CTRL_BURST;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	wd33c93_init(host, regs, dma_setup, dma_stop, WD33C93_FS_MHZ(20));
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	err = request_irq(irq, sgiwd93_intr, 0, "SGI WD93", host);
26662306a36Sopenharmony_ci	if (err) {
26762306a36Sopenharmony_ci		printk(KERN_WARNING "sgiwd93: Could not register irq %d "
26862306a36Sopenharmony_ci		       "for host %d.\n", irq, unit);
26962306a36Sopenharmony_ci		goto out_free;
27062306a36Sopenharmony_ci	}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	platform_set_drvdata(pdev, host);
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	err = scsi_add_host(host, NULL);
27562306a36Sopenharmony_ci	if (err)
27662306a36Sopenharmony_ci		goto out_irq;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	scsi_scan_host(host);
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	return 0;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ciout_irq:
28362306a36Sopenharmony_ci	free_irq(irq, host);
28462306a36Sopenharmony_ciout_free:
28562306a36Sopenharmony_ci	dma_free_noncoherent(&pdev->dev, HPC_DMA_SIZE, hdata->cpu, hdata->dma,
28662306a36Sopenharmony_ci			DMA_TO_DEVICE);
28762306a36Sopenharmony_ciout_put:
28862306a36Sopenharmony_ci	scsi_host_put(host);
28962306a36Sopenharmony_ciout:
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	return err;
29262306a36Sopenharmony_ci}
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_cistatic int sgiwd93_remove(struct platform_device *pdev)
29562306a36Sopenharmony_ci{
29662306a36Sopenharmony_ci	struct Scsi_Host *host = platform_get_drvdata(pdev);
29762306a36Sopenharmony_ci	struct ip22_hostdata *hdata = (struct ip22_hostdata *) host->hostdata;
29862306a36Sopenharmony_ci	struct sgiwd93_platform_data *pd = pdev->dev.platform_data;
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	scsi_remove_host(host);
30162306a36Sopenharmony_ci	free_irq(pd->irq, host);
30262306a36Sopenharmony_ci	dma_free_noncoherent(&pdev->dev, HPC_DMA_SIZE, hdata->cpu, hdata->dma,
30362306a36Sopenharmony_ci			DMA_TO_DEVICE);
30462306a36Sopenharmony_ci	scsi_host_put(host);
30562306a36Sopenharmony_ci	return 0;
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_cistatic struct platform_driver sgiwd93_driver = {
30962306a36Sopenharmony_ci	.probe  = sgiwd93_probe,
31062306a36Sopenharmony_ci	.remove = sgiwd93_remove,
31162306a36Sopenharmony_ci	.driver = {
31262306a36Sopenharmony_ci		.name   = "sgiwd93",
31362306a36Sopenharmony_ci	}
31462306a36Sopenharmony_ci};
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_cistatic int __init sgiwd93_module_init(void)
31762306a36Sopenharmony_ci{
31862306a36Sopenharmony_ci	return platform_driver_register(&sgiwd93_driver);
31962306a36Sopenharmony_ci}
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_cistatic void __exit sgiwd93_module_exit(void)
32262306a36Sopenharmony_ci{
32362306a36Sopenharmony_ci	return platform_driver_unregister(&sgiwd93_driver);
32462306a36Sopenharmony_ci}
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_cimodule_init(sgiwd93_module_init);
32762306a36Sopenharmony_cimodule_exit(sgiwd93_module_exit);
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ciMODULE_DESCRIPTION("SGI WD33C93 driver");
33062306a36Sopenharmony_ciMODULE_AUTHOR("Ralf Baechle <ralf@linux-mips.org>");
33162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
33262306a36Sopenharmony_ciMODULE_ALIAS("platform:sgiwd93");
333