18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
38c2ecf20Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
48c2ecf20Sopenharmony_ci * for more details.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Copyright (C) 1996 David S. Miller (davem@davemloft.net)
78c2ecf20Sopenharmony_ci * Copyright (C) 1999 Andrew R. Baker (andrewb@uab.edu)
88c2ecf20Sopenharmony_ci * Copyright (C) 2001 Florian Lohoff (flo@rfc822.org)
98c2ecf20Sopenharmony_ci * Copyright (C) 2003, 07 Ralf Baechle (ralf@linux-mips.org)
108c2ecf20Sopenharmony_ci *
118c2ecf20Sopenharmony_ci * (In all truth, Jed Schimmel wrote all this code.)
128c2ecf20Sopenharmony_ci */
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#undef DEBUG
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include <linux/delay.h>
178c2ecf20Sopenharmony_ci#include <linux/dma-mapping.h>
188c2ecf20Sopenharmony_ci#include <linux/gfp.h>
198c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
208c2ecf20Sopenharmony_ci#include <linux/init.h>
218c2ecf20Sopenharmony_ci#include <linux/kernel.h>
228c2ecf20Sopenharmony_ci#include <linux/types.h>
238c2ecf20Sopenharmony_ci#include <linux/module.h>
248c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
258c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#include <asm/sgi/hpc3.h>
288c2ecf20Sopenharmony_ci#include <asm/sgi/ip22.h>
298c2ecf20Sopenharmony_ci#include <asm/sgi/wd.h>
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#include "scsi.h"
328c2ecf20Sopenharmony_ci#include "wd33c93.h"
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_cistruct ip22_hostdata {
358c2ecf20Sopenharmony_ci	struct WD33C93_hostdata wh;
368c2ecf20Sopenharmony_ci	dma_addr_t dma;
378c2ecf20Sopenharmony_ci	void *cpu;
388c2ecf20Sopenharmony_ci	struct device *dev;
398c2ecf20Sopenharmony_ci};
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci#define host_to_hostdata(host) ((struct ip22_hostdata *)((host)->hostdata))
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistruct hpc_chunk {
448c2ecf20Sopenharmony_ci	struct hpc_dma_desc desc;
458c2ecf20Sopenharmony_ci	u32 _padding;	/* align to quadword boundary */
468c2ecf20Sopenharmony_ci};
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci/* space for hpc dma descriptors */
498c2ecf20Sopenharmony_ci#define HPC_DMA_SIZE   PAGE_SIZE
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci#define DMA_DIR(d)   ((d == DATA_OUT_DIR) ? DMA_TO_DEVICE : DMA_FROM_DEVICE)
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic irqreturn_t sgiwd93_intr(int irq, void *dev_id)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	struct Scsi_Host * host = dev_id;
568c2ecf20Sopenharmony_ci	unsigned long flags;
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	spin_lock_irqsave(host->host_lock, flags);
598c2ecf20Sopenharmony_ci	wd33c93_intr(host);
608c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(host->host_lock, flags);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_cistatic inline
668c2ecf20Sopenharmony_civoid fill_hpc_entries(struct ip22_hostdata *hd, struct scsi_cmnd *cmd, int din)
678c2ecf20Sopenharmony_ci{
688c2ecf20Sopenharmony_ci	unsigned long len = cmd->SCp.this_residual;
698c2ecf20Sopenharmony_ci	void *addr = cmd->SCp.ptr;
708c2ecf20Sopenharmony_ci	dma_addr_t physaddr;
718c2ecf20Sopenharmony_ci	unsigned long count;
728c2ecf20Sopenharmony_ci	struct hpc_chunk *hcp;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	physaddr = dma_map_single(hd->dev, addr, len, DMA_DIR(din));
758c2ecf20Sopenharmony_ci	cmd->SCp.dma_handle = physaddr;
768c2ecf20Sopenharmony_ci	hcp = hd->cpu;
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	while (len) {
798c2ecf20Sopenharmony_ci		/*
808c2ecf20Sopenharmony_ci		 * even cntinfo could be up to 16383, without
818c2ecf20Sopenharmony_ci		 * magic only 8192 works correctly
828c2ecf20Sopenharmony_ci		 */
838c2ecf20Sopenharmony_ci		count = len > 8192 ? 8192 : len;
848c2ecf20Sopenharmony_ci		hcp->desc.pbuf = physaddr;
858c2ecf20Sopenharmony_ci		hcp->desc.cntinfo = count;
868c2ecf20Sopenharmony_ci		hcp++;
878c2ecf20Sopenharmony_ci		len -= count;
888c2ecf20Sopenharmony_ci		physaddr += count;
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	/*
928c2ecf20Sopenharmony_ci	 * To make sure, if we trip an HPC bug, that we transfer every single
938c2ecf20Sopenharmony_ci	 * byte, we tag on an extra zero length dma descriptor at the end of
948c2ecf20Sopenharmony_ci	 * the chain.
958c2ecf20Sopenharmony_ci	 */
968c2ecf20Sopenharmony_ci	hcp->desc.pbuf = 0;
978c2ecf20Sopenharmony_ci	hcp->desc.cntinfo = HPCDMA_EOX;
988c2ecf20Sopenharmony_ci	dma_sync_single_for_device(hd->dev, hd->dma,
998c2ecf20Sopenharmony_ci		       (unsigned long)(hcp + 1) - (unsigned long)hd->cpu,
1008c2ecf20Sopenharmony_ci		       DMA_TO_DEVICE);
1018c2ecf20Sopenharmony_ci}
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_cistatic int dma_setup(struct scsi_cmnd *cmd, int datainp)
1048c2ecf20Sopenharmony_ci{
1058c2ecf20Sopenharmony_ci	struct ip22_hostdata *hdata = host_to_hostdata(cmd->device->host);
1068c2ecf20Sopenharmony_ci	struct hpc3_scsiregs *hregs =
1078c2ecf20Sopenharmony_ci		(struct hpc3_scsiregs *) cmd->device->host->base;
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	pr_debug("dma_setup: datainp<%d> hcp<%p> ", datainp, hdata->cpu);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	hdata->wh.dma_dir = datainp;
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci	/*
1148c2ecf20Sopenharmony_ci	 * wd33c93 shouldn't pass us bogus dma_setups, but it does:-(  The
1158c2ecf20Sopenharmony_ci	 * other wd33c93 drivers deal with it the same way (which isn't that
1168c2ecf20Sopenharmony_ci	 * obvious).  IMHO a better fix would be, not to do these dma setups
1178c2ecf20Sopenharmony_ci	 * in the first place.
1188c2ecf20Sopenharmony_ci	 */
1198c2ecf20Sopenharmony_ci	if (cmd->SCp.ptr == NULL || cmd->SCp.this_residual == 0)
1208c2ecf20Sopenharmony_ci		return 1;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	fill_hpc_entries(hdata, cmd, datainp);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	pr_debug(" HPCGO\n");
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	/* Start up the HPC. */
1278c2ecf20Sopenharmony_ci	hregs->ndptr = hdata->dma;
1288c2ecf20Sopenharmony_ci	if (datainp)
1298c2ecf20Sopenharmony_ci		hregs->ctrl = HPC3_SCTRL_ACTIVE;
1308c2ecf20Sopenharmony_ci	else
1318c2ecf20Sopenharmony_ci		hregs->ctrl = HPC3_SCTRL_ACTIVE | HPC3_SCTRL_DIR;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	return 0;
1348c2ecf20Sopenharmony_ci}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_cistatic void dma_stop(struct Scsi_Host *instance, struct scsi_cmnd *SCpnt,
1378c2ecf20Sopenharmony_ci		     int status)
1388c2ecf20Sopenharmony_ci{
1398c2ecf20Sopenharmony_ci	struct ip22_hostdata *hdata = host_to_hostdata(instance);
1408c2ecf20Sopenharmony_ci	struct hpc3_scsiregs *hregs;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	if (!SCpnt)
1438c2ecf20Sopenharmony_ci		return;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	if (SCpnt->SCp.ptr == NULL || SCpnt->SCp.this_residual == 0)
1468c2ecf20Sopenharmony_ci		return;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	hregs = (struct hpc3_scsiregs *) SCpnt->device->host->base;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	pr_debug("dma_stop: status<%d> ", status);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	/* First stop the HPC and flush it's FIFO. */
1538c2ecf20Sopenharmony_ci	if (hdata->wh.dma_dir) {
1548c2ecf20Sopenharmony_ci		hregs->ctrl |= HPC3_SCTRL_FLUSH;
1558c2ecf20Sopenharmony_ci		while (hregs->ctrl & HPC3_SCTRL_ACTIVE)
1568c2ecf20Sopenharmony_ci			barrier();
1578c2ecf20Sopenharmony_ci	}
1588c2ecf20Sopenharmony_ci	hregs->ctrl = 0;
1598c2ecf20Sopenharmony_ci	dma_unmap_single(hdata->dev, SCpnt->SCp.dma_handle,
1608c2ecf20Sopenharmony_ci			 SCpnt->SCp.this_residual,
1618c2ecf20Sopenharmony_ci			 DMA_DIR(hdata->wh.dma_dir));
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	pr_debug("\n");
1648c2ecf20Sopenharmony_ci}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_civoid sgiwd93_reset(unsigned long base)
1678c2ecf20Sopenharmony_ci{
1688c2ecf20Sopenharmony_ci	struct hpc3_scsiregs *hregs = (struct hpc3_scsiregs *) base;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	hregs->ctrl = HPC3_SCTRL_CRESET;
1718c2ecf20Sopenharmony_ci	udelay(50);
1728c2ecf20Sopenharmony_ci	hregs->ctrl = 0;
1738c2ecf20Sopenharmony_ci}
1748c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(sgiwd93_reset);
1758c2ecf20Sopenharmony_ci
1768c2ecf20Sopenharmony_cistatic inline void init_hpc_chain(struct ip22_hostdata *hdata)
1778c2ecf20Sopenharmony_ci{
1788c2ecf20Sopenharmony_ci	struct hpc_chunk *hcp = (struct hpc_chunk *)hdata->cpu;
1798c2ecf20Sopenharmony_ci	dma_addr_t dma = hdata->dma;
1808c2ecf20Sopenharmony_ci	unsigned long start, end;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	start = (unsigned long) hcp;
1838c2ecf20Sopenharmony_ci	end = start + HPC_DMA_SIZE;
1848c2ecf20Sopenharmony_ci	while (start < end) {
1858c2ecf20Sopenharmony_ci		hcp->desc.pnext = (u32) (dma + sizeof(struct hpc_chunk));
1868c2ecf20Sopenharmony_ci		hcp->desc.cntinfo = HPCDMA_EOX;
1878c2ecf20Sopenharmony_ci		hcp++;
1888c2ecf20Sopenharmony_ci		dma += sizeof(struct hpc_chunk);
1898c2ecf20Sopenharmony_ci		start += sizeof(struct hpc_chunk);
1908c2ecf20Sopenharmony_ci	}
1918c2ecf20Sopenharmony_ci	hcp--;
1928c2ecf20Sopenharmony_ci	hcp->desc.pnext = hdata->dma;
1938c2ecf20Sopenharmony_ci}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci/*
1968c2ecf20Sopenharmony_ci * Kludge alert - the SCSI code calls the abort and reset method with int
1978c2ecf20Sopenharmony_ci * arguments not with pointers.  So this is going to blow up beautyfully
1988c2ecf20Sopenharmony_ci * on 64-bit systems with memory outside the compat address spaces.
1998c2ecf20Sopenharmony_ci */
2008c2ecf20Sopenharmony_cistatic struct scsi_host_template sgiwd93_template = {
2018c2ecf20Sopenharmony_ci	.module			= THIS_MODULE,
2028c2ecf20Sopenharmony_ci	.proc_name		= "SGIWD93",
2038c2ecf20Sopenharmony_ci	.name			= "SGI WD93",
2048c2ecf20Sopenharmony_ci	.queuecommand		= wd33c93_queuecommand,
2058c2ecf20Sopenharmony_ci	.eh_abort_handler	= wd33c93_abort,
2068c2ecf20Sopenharmony_ci	.eh_host_reset_handler	= wd33c93_host_reset,
2078c2ecf20Sopenharmony_ci	.can_queue		= 16,
2088c2ecf20Sopenharmony_ci	.this_id		= 7,
2098c2ecf20Sopenharmony_ci	.sg_tablesize		= SG_ALL,
2108c2ecf20Sopenharmony_ci	.cmd_per_lun		= 8,
2118c2ecf20Sopenharmony_ci	.dma_boundary		= PAGE_SIZE - 1,
2128c2ecf20Sopenharmony_ci};
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_cistatic int sgiwd93_probe(struct platform_device *pdev)
2158c2ecf20Sopenharmony_ci{
2168c2ecf20Sopenharmony_ci	struct sgiwd93_platform_data *pd = pdev->dev.platform_data;
2178c2ecf20Sopenharmony_ci	unsigned char *wdregs = pd->wdregs;
2188c2ecf20Sopenharmony_ci	struct hpc3_scsiregs *hregs = pd->hregs;
2198c2ecf20Sopenharmony_ci	struct ip22_hostdata *hdata;
2208c2ecf20Sopenharmony_ci	struct Scsi_Host *host;
2218c2ecf20Sopenharmony_ci	wd33c93_regs regs;
2228c2ecf20Sopenharmony_ci	unsigned int unit = pd->unit;
2238c2ecf20Sopenharmony_ci	unsigned int irq = pd->irq;
2248c2ecf20Sopenharmony_ci	int err;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	host = scsi_host_alloc(&sgiwd93_template, sizeof(struct ip22_hostdata));
2278c2ecf20Sopenharmony_ci	if (!host) {
2288c2ecf20Sopenharmony_ci		err = -ENOMEM;
2298c2ecf20Sopenharmony_ci		goto out;
2308c2ecf20Sopenharmony_ci	}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	host->base = (unsigned long) hregs;
2338c2ecf20Sopenharmony_ci	host->irq = irq;
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	hdata = host_to_hostdata(host);
2368c2ecf20Sopenharmony_ci	hdata->dev = &pdev->dev;
2378c2ecf20Sopenharmony_ci	hdata->cpu = dma_alloc_noncoherent(&pdev->dev, HPC_DMA_SIZE,
2388c2ecf20Sopenharmony_ci				&hdata->dma, DMA_TO_DEVICE, GFP_KERNEL);
2398c2ecf20Sopenharmony_ci	if (!hdata->cpu) {
2408c2ecf20Sopenharmony_ci		printk(KERN_WARNING "sgiwd93: Could not allocate memory for "
2418c2ecf20Sopenharmony_ci		       "host %d buffer.\n", unit);
2428c2ecf20Sopenharmony_ci		err = -ENOMEM;
2438c2ecf20Sopenharmony_ci		goto out_put;
2448c2ecf20Sopenharmony_ci	}
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	init_hpc_chain(hdata);
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	regs.SASR = wdregs + 3;
2498c2ecf20Sopenharmony_ci	regs.SCMD = wdregs + 7;
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	hdata->wh.no_sync = 0;
2528c2ecf20Sopenharmony_ci	hdata->wh.fast = 1;
2538c2ecf20Sopenharmony_ci	hdata->wh.dma_mode = CTRL_BURST;
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci	wd33c93_init(host, regs, dma_setup, dma_stop, WD33C93_FS_MHZ(20));
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_ci	err = request_irq(irq, sgiwd93_intr, 0, "SGI WD93", host);
2588c2ecf20Sopenharmony_ci	if (err) {
2598c2ecf20Sopenharmony_ci		printk(KERN_WARNING "sgiwd93: Could not register irq %d "
2608c2ecf20Sopenharmony_ci		       "for host %d.\n", irq, unit);
2618c2ecf20Sopenharmony_ci		goto out_free;
2628c2ecf20Sopenharmony_ci	}
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, host);
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	err = scsi_add_host(host, NULL);
2678c2ecf20Sopenharmony_ci	if (err)
2688c2ecf20Sopenharmony_ci		goto out_irq;
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	scsi_scan_host(host);
2718c2ecf20Sopenharmony_ci
2728c2ecf20Sopenharmony_ci	return 0;
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ciout_irq:
2758c2ecf20Sopenharmony_ci	free_irq(irq, host);
2768c2ecf20Sopenharmony_ciout_free:
2778c2ecf20Sopenharmony_ci	dma_free_noncoherent(&pdev->dev, HPC_DMA_SIZE, hdata->cpu, hdata->dma,
2788c2ecf20Sopenharmony_ci			DMA_TO_DEVICE);
2798c2ecf20Sopenharmony_ciout_put:
2808c2ecf20Sopenharmony_ci	scsi_host_put(host);
2818c2ecf20Sopenharmony_ciout:
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	return err;
2848c2ecf20Sopenharmony_ci}
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_cistatic int sgiwd93_remove(struct platform_device *pdev)
2878c2ecf20Sopenharmony_ci{
2888c2ecf20Sopenharmony_ci	struct Scsi_Host *host = platform_get_drvdata(pdev);
2898c2ecf20Sopenharmony_ci	struct ip22_hostdata *hdata = (struct ip22_hostdata *) host->hostdata;
2908c2ecf20Sopenharmony_ci	struct sgiwd93_platform_data *pd = pdev->dev.platform_data;
2918c2ecf20Sopenharmony_ci
2928c2ecf20Sopenharmony_ci	scsi_remove_host(host);
2938c2ecf20Sopenharmony_ci	free_irq(pd->irq, host);
2948c2ecf20Sopenharmony_ci	dma_free_noncoherent(&pdev->dev, HPC_DMA_SIZE, hdata->cpu, hdata->dma,
2958c2ecf20Sopenharmony_ci			DMA_TO_DEVICE);
2968c2ecf20Sopenharmony_ci	scsi_host_put(host);
2978c2ecf20Sopenharmony_ci	return 0;
2988c2ecf20Sopenharmony_ci}
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_cistatic struct platform_driver sgiwd93_driver = {
3018c2ecf20Sopenharmony_ci	.probe  = sgiwd93_probe,
3028c2ecf20Sopenharmony_ci	.remove = sgiwd93_remove,
3038c2ecf20Sopenharmony_ci	.driver = {
3048c2ecf20Sopenharmony_ci		.name   = "sgiwd93",
3058c2ecf20Sopenharmony_ci	}
3068c2ecf20Sopenharmony_ci};
3078c2ecf20Sopenharmony_ci
3088c2ecf20Sopenharmony_cistatic int __init sgiwd93_module_init(void)
3098c2ecf20Sopenharmony_ci{
3108c2ecf20Sopenharmony_ci	return platform_driver_register(&sgiwd93_driver);
3118c2ecf20Sopenharmony_ci}
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_cistatic void __exit sgiwd93_module_exit(void)
3148c2ecf20Sopenharmony_ci{
3158c2ecf20Sopenharmony_ci	return platform_driver_unregister(&sgiwd93_driver);
3168c2ecf20Sopenharmony_ci}
3178c2ecf20Sopenharmony_ci
3188c2ecf20Sopenharmony_cimodule_init(sgiwd93_module_init);
3198c2ecf20Sopenharmony_cimodule_exit(sgiwd93_module_exit);
3208c2ecf20Sopenharmony_ci
3218c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("SGI WD33C93 driver");
3228c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ralf Baechle <ralf@linux-mips.org>");
3238c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
3248c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:sgiwd93");
325