18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci#include <linux/types.h>
38c2ecf20Sopenharmony_ci#include <linux/mm.h>
48c2ecf20Sopenharmony_ci#include <linux/ioport.h>
58c2ecf20Sopenharmony_ci#include <linux/init.h>
68c2ecf20Sopenharmony_ci#include <linux/slab.h>
78c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
88c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
98c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <asm/page.h>
138c2ecf20Sopenharmony_ci#include <asm/amigaints.h>
148c2ecf20Sopenharmony_ci#include <asm/amigahw.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include "scsi.h"
178c2ecf20Sopenharmony_ci#include "wd33c93.h"
188c2ecf20Sopenharmony_ci#include "a3000.h"
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_cistruct a3000_hostdata {
228c2ecf20Sopenharmony_ci	struct WD33C93_hostdata wh;
238c2ecf20Sopenharmony_ci	struct a3000_scsiregs *regs;
248c2ecf20Sopenharmony_ci};
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic irqreturn_t a3000_intr(int irq, void *data)
278c2ecf20Sopenharmony_ci{
288c2ecf20Sopenharmony_ci	struct Scsi_Host *instance = data;
298c2ecf20Sopenharmony_ci	struct a3000_hostdata *hdata = shost_priv(instance);
308c2ecf20Sopenharmony_ci	unsigned int status = hdata->regs->ISTR;
318c2ecf20Sopenharmony_ci	unsigned long flags;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	if (!(status & ISTR_INT_P))
348c2ecf20Sopenharmony_ci		return IRQ_NONE;
358c2ecf20Sopenharmony_ci	if (status & ISTR_INTS) {
368c2ecf20Sopenharmony_ci		spin_lock_irqsave(instance->host_lock, flags);
378c2ecf20Sopenharmony_ci		wd33c93_intr(instance);
388c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(instance->host_lock, flags);
398c2ecf20Sopenharmony_ci		return IRQ_HANDLED;
408c2ecf20Sopenharmony_ci	}
418c2ecf20Sopenharmony_ci	pr_warn("Non-serviced A3000 SCSI-interrupt? ISTR = %02x\n", status);
428c2ecf20Sopenharmony_ci	return IRQ_NONE;
438c2ecf20Sopenharmony_ci}
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistatic int dma_setup(struct scsi_cmnd *cmd, int dir_in)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	struct Scsi_Host *instance = cmd->device->host;
488c2ecf20Sopenharmony_ci	struct a3000_hostdata *hdata = shost_priv(instance);
498c2ecf20Sopenharmony_ci	struct WD33C93_hostdata *wh = &hdata->wh;
508c2ecf20Sopenharmony_ci	struct a3000_scsiregs *regs = hdata->regs;
518c2ecf20Sopenharmony_ci	unsigned short cntr = CNTR_PDMD | CNTR_INTEN;
528c2ecf20Sopenharmony_ci	unsigned long addr = virt_to_bus(cmd->SCp.ptr);
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	/*
558c2ecf20Sopenharmony_ci	 * if the physical address has the wrong alignment, or if
568c2ecf20Sopenharmony_ci	 * physical address is bad, or if it is a write and at the
578c2ecf20Sopenharmony_ci	 * end of a physical memory chunk, then allocate a bounce
588c2ecf20Sopenharmony_ci	 * buffer
598c2ecf20Sopenharmony_ci	 */
608c2ecf20Sopenharmony_ci	if (addr & A3000_XFER_MASK) {
618c2ecf20Sopenharmony_ci		wh->dma_bounce_len = (cmd->SCp.this_residual + 511) & ~0x1ff;
628c2ecf20Sopenharmony_ci		wh->dma_bounce_buffer = kmalloc(wh->dma_bounce_len,
638c2ecf20Sopenharmony_ci						GFP_KERNEL);
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci		/* can't allocate memory; use PIO */
668c2ecf20Sopenharmony_ci		if (!wh->dma_bounce_buffer) {
678c2ecf20Sopenharmony_ci			wh->dma_bounce_len = 0;
688c2ecf20Sopenharmony_ci			return 1;
698c2ecf20Sopenharmony_ci		}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci		if (!dir_in) {
728c2ecf20Sopenharmony_ci			/* copy to bounce buffer for a write */
738c2ecf20Sopenharmony_ci			memcpy(wh->dma_bounce_buffer, cmd->SCp.ptr,
748c2ecf20Sopenharmony_ci			       cmd->SCp.this_residual);
758c2ecf20Sopenharmony_ci		}
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci		addr = virt_to_bus(wh->dma_bounce_buffer);
788c2ecf20Sopenharmony_ci	}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	/* setup dma direction */
818c2ecf20Sopenharmony_ci	if (!dir_in)
828c2ecf20Sopenharmony_ci		cntr |= CNTR_DDIR;
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	/* remember direction */
858c2ecf20Sopenharmony_ci	wh->dma_dir = dir_in;
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	regs->CNTR = cntr;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	/* setup DMA *physical* address */
908c2ecf20Sopenharmony_ci	regs->ACR = addr;
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	if (dir_in) {
938c2ecf20Sopenharmony_ci		/* invalidate any cache */
948c2ecf20Sopenharmony_ci		cache_clear(addr, cmd->SCp.this_residual);
958c2ecf20Sopenharmony_ci	} else {
968c2ecf20Sopenharmony_ci		/* push any dirty cache */
978c2ecf20Sopenharmony_ci		cache_push(addr, cmd->SCp.this_residual);
988c2ecf20Sopenharmony_ci	}
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	/* start DMA */
1018c2ecf20Sopenharmony_ci	mb();			/* make sure setup is completed */
1028c2ecf20Sopenharmony_ci	regs->ST_DMA = 1;
1038c2ecf20Sopenharmony_ci	mb();			/* make sure DMA has started before next IO */
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	/* return success */
1068c2ecf20Sopenharmony_ci	return 0;
1078c2ecf20Sopenharmony_ci}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cistatic void dma_stop(struct Scsi_Host *instance, struct scsi_cmnd *SCpnt,
1108c2ecf20Sopenharmony_ci		     int status)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	struct a3000_hostdata *hdata = shost_priv(instance);
1138c2ecf20Sopenharmony_ci	struct WD33C93_hostdata *wh = &hdata->wh;
1148c2ecf20Sopenharmony_ci	struct a3000_scsiregs *regs = hdata->regs;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	/* disable SCSI interrupts */
1178c2ecf20Sopenharmony_ci	unsigned short cntr = CNTR_PDMD;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	if (!wh->dma_dir)
1208c2ecf20Sopenharmony_ci		cntr |= CNTR_DDIR;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	regs->CNTR = cntr;
1238c2ecf20Sopenharmony_ci	mb();			/* make sure CNTR is updated before next IO */
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	/* flush if we were reading */
1268c2ecf20Sopenharmony_ci	if (wh->dma_dir) {
1278c2ecf20Sopenharmony_ci		regs->FLUSH = 1;
1288c2ecf20Sopenharmony_ci		mb();		/* don't allow prefetch */
1298c2ecf20Sopenharmony_ci		while (!(regs->ISTR & ISTR_FE_FLG))
1308c2ecf20Sopenharmony_ci			barrier();
1318c2ecf20Sopenharmony_ci		mb();		/* no IO until FLUSH is done */
1328c2ecf20Sopenharmony_ci	}
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	/* clear a possible interrupt */
1358c2ecf20Sopenharmony_ci	/* I think that this CINT is only necessary if you are
1368c2ecf20Sopenharmony_ci	 * using the terminal count features.   HM 7 Mar 1994
1378c2ecf20Sopenharmony_ci	 */
1388c2ecf20Sopenharmony_ci	regs->CINT = 1;
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	/* stop DMA */
1418c2ecf20Sopenharmony_ci	regs->SP_DMA = 1;
1428c2ecf20Sopenharmony_ci	mb();			/* make sure DMA is stopped before next IO */
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	/* restore the CONTROL bits (minus the direction flag) */
1458c2ecf20Sopenharmony_ci	regs->CNTR = CNTR_PDMD | CNTR_INTEN;
1468c2ecf20Sopenharmony_ci	mb();			/* make sure CNTR is updated before next IO */
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	/* copy from a bounce buffer, if necessary */
1498c2ecf20Sopenharmony_ci	if (status && wh->dma_bounce_buffer) {
1508c2ecf20Sopenharmony_ci		if (SCpnt) {
1518c2ecf20Sopenharmony_ci			if (wh->dma_dir && SCpnt)
1528c2ecf20Sopenharmony_ci				memcpy(SCpnt->SCp.ptr, wh->dma_bounce_buffer,
1538c2ecf20Sopenharmony_ci				       SCpnt->SCp.this_residual);
1548c2ecf20Sopenharmony_ci			kfree(wh->dma_bounce_buffer);
1558c2ecf20Sopenharmony_ci			wh->dma_bounce_buffer = NULL;
1568c2ecf20Sopenharmony_ci			wh->dma_bounce_len = 0;
1578c2ecf20Sopenharmony_ci		} else {
1588c2ecf20Sopenharmony_ci			kfree(wh->dma_bounce_buffer);
1598c2ecf20Sopenharmony_ci			wh->dma_bounce_buffer = NULL;
1608c2ecf20Sopenharmony_ci			wh->dma_bounce_len = 0;
1618c2ecf20Sopenharmony_ci		}
1628c2ecf20Sopenharmony_ci	}
1638c2ecf20Sopenharmony_ci}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_cistatic struct scsi_host_template amiga_a3000_scsi_template = {
1668c2ecf20Sopenharmony_ci	.module			= THIS_MODULE,
1678c2ecf20Sopenharmony_ci	.name			= "Amiga 3000 built-in SCSI",
1688c2ecf20Sopenharmony_ci	.show_info		= wd33c93_show_info,
1698c2ecf20Sopenharmony_ci	.write_info		= wd33c93_write_info,
1708c2ecf20Sopenharmony_ci	.proc_name		= "A3000",
1718c2ecf20Sopenharmony_ci	.queuecommand		= wd33c93_queuecommand,
1728c2ecf20Sopenharmony_ci	.eh_abort_handler	= wd33c93_abort,
1738c2ecf20Sopenharmony_ci	.eh_host_reset_handler	= wd33c93_host_reset,
1748c2ecf20Sopenharmony_ci	.can_queue		= CAN_QUEUE,
1758c2ecf20Sopenharmony_ci	.this_id		= 7,
1768c2ecf20Sopenharmony_ci	.sg_tablesize		= SG_ALL,
1778c2ecf20Sopenharmony_ci	.cmd_per_lun		= CMD_PER_LUN,
1788c2ecf20Sopenharmony_ci};
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic int __init amiga_a3000_scsi_probe(struct platform_device *pdev)
1818c2ecf20Sopenharmony_ci{
1828c2ecf20Sopenharmony_ci	struct resource *res;
1838c2ecf20Sopenharmony_ci	struct Scsi_Host *instance;
1848c2ecf20Sopenharmony_ci	int error;
1858c2ecf20Sopenharmony_ci	struct a3000_scsiregs *regs;
1868c2ecf20Sopenharmony_ci	wd33c93_regs wdregs;
1878c2ecf20Sopenharmony_ci	struct a3000_hostdata *hdata;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1908c2ecf20Sopenharmony_ci	if (!res)
1918c2ecf20Sopenharmony_ci		return -ENODEV;
1928c2ecf20Sopenharmony_ci
1938c2ecf20Sopenharmony_ci	if (!request_mem_region(res->start, resource_size(res), "wd33c93"))
1948c2ecf20Sopenharmony_ci		return -EBUSY;
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	instance = scsi_host_alloc(&amiga_a3000_scsi_template,
1978c2ecf20Sopenharmony_ci				   sizeof(struct a3000_hostdata));
1988c2ecf20Sopenharmony_ci	if (!instance) {
1998c2ecf20Sopenharmony_ci		error = -ENOMEM;
2008c2ecf20Sopenharmony_ci		goto fail_alloc;
2018c2ecf20Sopenharmony_ci	}
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci	instance->irq = IRQ_AMIGA_PORTS;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	regs = ZTWO_VADDR(res->start);
2068c2ecf20Sopenharmony_ci	regs->DAWR = DAWR_A3000;
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	wdregs.SASR = &regs->SASR;
2098c2ecf20Sopenharmony_ci	wdregs.SCMD = &regs->SCMD;
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	hdata = shost_priv(instance);
2128c2ecf20Sopenharmony_ci	hdata->wh.no_sync = 0xff;
2138c2ecf20Sopenharmony_ci	hdata->wh.fast = 0;
2148c2ecf20Sopenharmony_ci	hdata->wh.dma_mode = CTRL_DMA;
2158c2ecf20Sopenharmony_ci	hdata->regs = regs;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	wd33c93_init(instance, wdregs, dma_setup, dma_stop, WD33C93_FS_12_15);
2188c2ecf20Sopenharmony_ci	error = request_irq(IRQ_AMIGA_PORTS, a3000_intr, IRQF_SHARED,
2198c2ecf20Sopenharmony_ci			    "A3000 SCSI", instance);
2208c2ecf20Sopenharmony_ci	if (error)
2218c2ecf20Sopenharmony_ci		goto fail_irq;
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_ci	regs->CNTR = CNTR_PDMD | CNTR_INTEN;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	error = scsi_add_host(instance, NULL);
2268c2ecf20Sopenharmony_ci	if (error)
2278c2ecf20Sopenharmony_ci		goto fail_host;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, instance);
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	scsi_scan_host(instance);
2328c2ecf20Sopenharmony_ci	return 0;
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_cifail_host:
2358c2ecf20Sopenharmony_ci	free_irq(IRQ_AMIGA_PORTS, instance);
2368c2ecf20Sopenharmony_cifail_irq:
2378c2ecf20Sopenharmony_ci	scsi_host_put(instance);
2388c2ecf20Sopenharmony_cifail_alloc:
2398c2ecf20Sopenharmony_ci	release_mem_region(res->start, resource_size(res));
2408c2ecf20Sopenharmony_ci	return error;
2418c2ecf20Sopenharmony_ci}
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_cistatic int __exit amiga_a3000_scsi_remove(struct platform_device *pdev)
2448c2ecf20Sopenharmony_ci{
2458c2ecf20Sopenharmony_ci	struct Scsi_Host *instance = platform_get_drvdata(pdev);
2468c2ecf20Sopenharmony_ci	struct a3000_hostdata *hdata = shost_priv(instance);
2478c2ecf20Sopenharmony_ci	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	hdata->regs->CNTR = 0;
2508c2ecf20Sopenharmony_ci	scsi_remove_host(instance);
2518c2ecf20Sopenharmony_ci	free_irq(IRQ_AMIGA_PORTS, instance);
2528c2ecf20Sopenharmony_ci	scsi_host_put(instance);
2538c2ecf20Sopenharmony_ci	release_mem_region(res->start, resource_size(res));
2548c2ecf20Sopenharmony_ci	return 0;
2558c2ecf20Sopenharmony_ci}
2568c2ecf20Sopenharmony_ci
2578c2ecf20Sopenharmony_cistatic struct platform_driver amiga_a3000_scsi_driver = {
2588c2ecf20Sopenharmony_ci	.remove = __exit_p(amiga_a3000_scsi_remove),
2598c2ecf20Sopenharmony_ci	.driver   = {
2608c2ecf20Sopenharmony_ci		.name	= "amiga-a3000-scsi",
2618c2ecf20Sopenharmony_ci	},
2628c2ecf20Sopenharmony_ci};
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_cimodule_platform_driver_probe(amiga_a3000_scsi_driver, amiga_a3000_scsi_probe);
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Amiga 3000 built-in SCSI");
2678c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2688c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:amiga-a3000-scsi");
269