162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci#include <linux/types.h>
362306a36Sopenharmony_ci#include <linux/mm.h>
462306a36Sopenharmony_ci#include <linux/ioport.h>
562306a36Sopenharmony_ci#include <linux/init.h>
662306a36Sopenharmony_ci#include <linux/slab.h>
762306a36Sopenharmony_ci#include <linux/spinlock.h>
862306a36Sopenharmony_ci#include <linux/interrupt.h>
962306a36Sopenharmony_ci#include <linux/platform_device.h>
1062306a36Sopenharmony_ci#include <linux/dma-mapping.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#include <asm/page.h>
1462306a36Sopenharmony_ci#include <asm/amigaints.h>
1562306a36Sopenharmony_ci#include <asm/amigahw.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <scsi/scsi.h>
1862306a36Sopenharmony_ci#include <scsi/scsi_cmnd.h>
1962306a36Sopenharmony_ci#include <scsi/scsi_device.h>
2062306a36Sopenharmony_ci#include <scsi/scsi_eh.h>
2162306a36Sopenharmony_ci#include <scsi/scsi_tcq.h>
2262306a36Sopenharmony_ci#include "wd33c93.h"
2362306a36Sopenharmony_ci#include "a3000.h"
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistruct a3000_hostdata {
2762306a36Sopenharmony_ci	struct WD33C93_hostdata wh;
2862306a36Sopenharmony_ci	struct a3000_scsiregs *regs;
2962306a36Sopenharmony_ci	struct device *dev;
3062306a36Sopenharmony_ci};
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define DMA_DIR(d)   ((d == DATA_OUT_DIR) ? DMA_TO_DEVICE : DMA_FROM_DEVICE)
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistatic irqreturn_t a3000_intr(int irq, void *data)
3562306a36Sopenharmony_ci{
3662306a36Sopenharmony_ci	struct Scsi_Host *instance = data;
3762306a36Sopenharmony_ci	struct a3000_hostdata *hdata = shost_priv(instance);
3862306a36Sopenharmony_ci	unsigned int status = hdata->regs->ISTR;
3962306a36Sopenharmony_ci	unsigned long flags;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci	if (!(status & ISTR_INT_P))
4262306a36Sopenharmony_ci		return IRQ_NONE;
4362306a36Sopenharmony_ci	if (status & ISTR_INTS) {
4462306a36Sopenharmony_ci		spin_lock_irqsave(instance->host_lock, flags);
4562306a36Sopenharmony_ci		wd33c93_intr(instance);
4662306a36Sopenharmony_ci		spin_unlock_irqrestore(instance->host_lock, flags);
4762306a36Sopenharmony_ci		return IRQ_HANDLED;
4862306a36Sopenharmony_ci	}
4962306a36Sopenharmony_ci	pr_warn("Non-serviced A3000 SCSI-interrupt? ISTR = %02x\n", status);
5062306a36Sopenharmony_ci	return IRQ_NONE;
5162306a36Sopenharmony_ci}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic int dma_setup(struct scsi_cmnd *cmd, int dir_in)
5462306a36Sopenharmony_ci{
5562306a36Sopenharmony_ci	struct scsi_pointer *scsi_pointer = WD33C93_scsi_pointer(cmd);
5662306a36Sopenharmony_ci	unsigned long len = scsi_pointer->this_residual;
5762306a36Sopenharmony_ci	struct Scsi_Host *instance = cmd->device->host;
5862306a36Sopenharmony_ci	struct a3000_hostdata *hdata = shost_priv(instance);
5962306a36Sopenharmony_ci	struct WD33C93_hostdata *wh = &hdata->wh;
6062306a36Sopenharmony_ci	struct a3000_scsiregs *regs = hdata->regs;
6162306a36Sopenharmony_ci	unsigned short cntr = CNTR_PDMD | CNTR_INTEN;
6262306a36Sopenharmony_ci	dma_addr_t addr;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	addr = dma_map_single(hdata->dev, scsi_pointer->ptr,
6562306a36Sopenharmony_ci			      len, DMA_DIR(dir_in));
6662306a36Sopenharmony_ci	if (dma_mapping_error(hdata->dev, addr)) {
6762306a36Sopenharmony_ci		dev_warn(hdata->dev, "cannot map SCSI data block %p\n",
6862306a36Sopenharmony_ci			 scsi_pointer->ptr);
6962306a36Sopenharmony_ci		return 1;
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci	scsi_pointer->dma_handle = addr;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	/*
7462306a36Sopenharmony_ci	 * if the physical address has the wrong alignment, or if
7562306a36Sopenharmony_ci	 * physical address is bad, or if it is a write and at the
7662306a36Sopenharmony_ci	 * end of a physical memory chunk, then allocate a bounce
7762306a36Sopenharmony_ci	 * buffer
7862306a36Sopenharmony_ci	 * MSch 20220629 - only wrong alignment tested - bounce
7962306a36Sopenharmony_ci	 * buffer returned by kmalloc is guaranteed to be aligned
8062306a36Sopenharmony_ci	 */
8162306a36Sopenharmony_ci	if (addr & A3000_XFER_MASK) {
8262306a36Sopenharmony_ci		WARN_ONCE(1, "Invalid alignment for DMA!");
8362306a36Sopenharmony_ci		/* drop useless mapping */
8462306a36Sopenharmony_ci		dma_unmap_single(hdata->dev, scsi_pointer->dma_handle,
8562306a36Sopenharmony_ci				 scsi_pointer->this_residual,
8662306a36Sopenharmony_ci				 DMA_DIR(dir_in));
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci		wh->dma_bounce_len = (scsi_pointer->this_residual + 511) & ~0x1ff;
8962306a36Sopenharmony_ci		wh->dma_bounce_buffer = kmalloc(wh->dma_bounce_len,
9062306a36Sopenharmony_ci						GFP_KERNEL);
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci		/* can't allocate memory; use PIO */
9362306a36Sopenharmony_ci		if (!wh->dma_bounce_buffer) {
9462306a36Sopenharmony_ci			wh->dma_bounce_len = 0;
9562306a36Sopenharmony_ci			scsi_pointer->dma_handle = (dma_addr_t) NULL;
9662306a36Sopenharmony_ci			return 1;
9762306a36Sopenharmony_ci		}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci		if (!dir_in) {
10062306a36Sopenharmony_ci			/* copy to bounce buffer for a write */
10162306a36Sopenharmony_ci			memcpy(wh->dma_bounce_buffer, scsi_pointer->ptr,
10262306a36Sopenharmony_ci			       scsi_pointer->this_residual);
10362306a36Sopenharmony_ci		}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci		addr = dma_map_single(hdata->dev, scsi_pointer->ptr,
10662306a36Sopenharmony_ci				      len, DMA_DIR(dir_in));
10762306a36Sopenharmony_ci		if (dma_mapping_error(hdata->dev, addr)) {
10862306a36Sopenharmony_ci			dev_warn(hdata->dev,
10962306a36Sopenharmony_ci				 "cannot map SCSI data block %p\n",
11062306a36Sopenharmony_ci				 scsi_pointer->ptr);
11162306a36Sopenharmony_ci			return 1;
11262306a36Sopenharmony_ci		}
11362306a36Sopenharmony_ci		scsi_pointer->dma_handle = addr;
11462306a36Sopenharmony_ci	}
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	/* setup dma direction */
11762306a36Sopenharmony_ci	if (!dir_in)
11862306a36Sopenharmony_ci		cntr |= CNTR_DDIR;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	/* remember direction */
12162306a36Sopenharmony_ci	wh->dma_dir = dir_in;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	regs->CNTR = cntr;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	/* setup DMA *physical* address */
12662306a36Sopenharmony_ci	regs->ACR = addr;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	/* no more cache flush here - dma_map_single() takes care */
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	/* start DMA */
13162306a36Sopenharmony_ci	mb();			/* make sure setup is completed */
13262306a36Sopenharmony_ci	regs->ST_DMA = 1;
13362306a36Sopenharmony_ci	mb();			/* make sure DMA has started before next IO */
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	/* return success */
13662306a36Sopenharmony_ci	return 0;
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic void dma_stop(struct Scsi_Host *instance, struct scsi_cmnd *SCpnt,
14062306a36Sopenharmony_ci		     int status)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	struct scsi_pointer *scsi_pointer = WD33C93_scsi_pointer(SCpnt);
14362306a36Sopenharmony_ci	struct a3000_hostdata *hdata = shost_priv(instance);
14462306a36Sopenharmony_ci	struct WD33C93_hostdata *wh = &hdata->wh;
14562306a36Sopenharmony_ci	struct a3000_scsiregs *regs = hdata->regs;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	/* disable SCSI interrupts */
14862306a36Sopenharmony_ci	unsigned short cntr = CNTR_PDMD;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	if (!wh->dma_dir)
15162306a36Sopenharmony_ci		cntr |= CNTR_DDIR;
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_ci	regs->CNTR = cntr;
15462306a36Sopenharmony_ci	mb();			/* make sure CNTR is updated before next IO */
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	/* flush if we were reading */
15762306a36Sopenharmony_ci	if (wh->dma_dir) {
15862306a36Sopenharmony_ci		regs->FLUSH = 1;
15962306a36Sopenharmony_ci		mb();		/* don't allow prefetch */
16062306a36Sopenharmony_ci		while (!(regs->ISTR & ISTR_FE_FLG))
16162306a36Sopenharmony_ci			barrier();
16262306a36Sopenharmony_ci		mb();		/* no IO until FLUSH is done */
16362306a36Sopenharmony_ci	}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	/* clear a possible interrupt */
16662306a36Sopenharmony_ci	/* I think that this CINT is only necessary if you are
16762306a36Sopenharmony_ci	 * using the terminal count features.   HM 7 Mar 1994
16862306a36Sopenharmony_ci	 */
16962306a36Sopenharmony_ci	regs->CINT = 1;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	/* stop DMA */
17262306a36Sopenharmony_ci	regs->SP_DMA = 1;
17362306a36Sopenharmony_ci	mb();			/* make sure DMA is stopped before next IO */
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	/* restore the CONTROL bits (minus the direction flag) */
17662306a36Sopenharmony_ci	regs->CNTR = CNTR_PDMD | CNTR_INTEN;
17762306a36Sopenharmony_ci	mb();			/* make sure CNTR is updated before next IO */
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	dma_unmap_single(hdata->dev, scsi_pointer->dma_handle,
18062306a36Sopenharmony_ci			 scsi_pointer->this_residual,
18162306a36Sopenharmony_ci			 DMA_DIR(wh->dma_dir));
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	/* copy from a bounce buffer, if necessary */
18462306a36Sopenharmony_ci	if (status && wh->dma_bounce_buffer) {
18562306a36Sopenharmony_ci		if (SCpnt) {
18662306a36Sopenharmony_ci			if (wh->dma_dir && SCpnt)
18762306a36Sopenharmony_ci				memcpy(scsi_pointer->ptr, wh->dma_bounce_buffer,
18862306a36Sopenharmony_ci				       scsi_pointer->this_residual);
18962306a36Sopenharmony_ci			kfree(wh->dma_bounce_buffer);
19062306a36Sopenharmony_ci			wh->dma_bounce_buffer = NULL;
19162306a36Sopenharmony_ci			wh->dma_bounce_len = 0;
19262306a36Sopenharmony_ci		} else {
19362306a36Sopenharmony_ci			kfree(wh->dma_bounce_buffer);
19462306a36Sopenharmony_ci			wh->dma_bounce_buffer = NULL;
19562306a36Sopenharmony_ci			wh->dma_bounce_len = 0;
19662306a36Sopenharmony_ci		}
19762306a36Sopenharmony_ci	}
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic const struct scsi_host_template amiga_a3000_scsi_template = {
20162306a36Sopenharmony_ci	.module			= THIS_MODULE,
20262306a36Sopenharmony_ci	.name			= "Amiga 3000 built-in SCSI",
20362306a36Sopenharmony_ci	.show_info		= wd33c93_show_info,
20462306a36Sopenharmony_ci	.write_info		= wd33c93_write_info,
20562306a36Sopenharmony_ci	.proc_name		= "A3000",
20662306a36Sopenharmony_ci	.queuecommand		= wd33c93_queuecommand,
20762306a36Sopenharmony_ci	.eh_abort_handler	= wd33c93_abort,
20862306a36Sopenharmony_ci	.eh_host_reset_handler	= wd33c93_host_reset,
20962306a36Sopenharmony_ci	.can_queue		= CAN_QUEUE,
21062306a36Sopenharmony_ci	.this_id		= 7,
21162306a36Sopenharmony_ci	.sg_tablesize		= SG_ALL,
21262306a36Sopenharmony_ci	.cmd_per_lun		= CMD_PER_LUN,
21362306a36Sopenharmony_ci	.cmd_size		= sizeof(struct scsi_pointer),
21462306a36Sopenharmony_ci};
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_cistatic int __init amiga_a3000_scsi_probe(struct platform_device *pdev)
21762306a36Sopenharmony_ci{
21862306a36Sopenharmony_ci	struct resource *res;
21962306a36Sopenharmony_ci	struct Scsi_Host *instance;
22062306a36Sopenharmony_ci	int error;
22162306a36Sopenharmony_ci	struct a3000_scsiregs *regs;
22262306a36Sopenharmony_ci	wd33c93_regs wdregs;
22362306a36Sopenharmony_ci	struct a3000_hostdata *hdata;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	if (dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32))) {
22662306a36Sopenharmony_ci		dev_warn(&pdev->dev, "cannot use 32 bit DMA\n");
22762306a36Sopenharmony_ci		return -ENODEV;
22862306a36Sopenharmony_ci	}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
23162306a36Sopenharmony_ci	if (!res)
23262306a36Sopenharmony_ci		return -ENODEV;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	if (!request_mem_region(res->start, resource_size(res), "wd33c93"))
23562306a36Sopenharmony_ci		return -EBUSY;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	instance = scsi_host_alloc(&amiga_a3000_scsi_template,
23862306a36Sopenharmony_ci				   sizeof(struct a3000_hostdata));
23962306a36Sopenharmony_ci	if (!instance) {
24062306a36Sopenharmony_ci		error = -ENOMEM;
24162306a36Sopenharmony_ci		goto fail_alloc;
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	instance->irq = IRQ_AMIGA_PORTS;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	regs = ZTWO_VADDR(res->start);
24762306a36Sopenharmony_ci	regs->DAWR = DAWR_A3000;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	wdregs.SASR = &regs->SASR;
25062306a36Sopenharmony_ci	wdregs.SCMD = &regs->SCMD;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	hdata = shost_priv(instance);
25362306a36Sopenharmony_ci	hdata->dev = &pdev->dev;
25462306a36Sopenharmony_ci	hdata->wh.no_sync = 0xff;
25562306a36Sopenharmony_ci	hdata->wh.fast = 0;
25662306a36Sopenharmony_ci	hdata->wh.dma_mode = CTRL_DMA;
25762306a36Sopenharmony_ci	hdata->regs = regs;
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	wd33c93_init(instance, wdregs, dma_setup, dma_stop, WD33C93_FS_12_15);
26062306a36Sopenharmony_ci	error = request_irq(IRQ_AMIGA_PORTS, a3000_intr, IRQF_SHARED,
26162306a36Sopenharmony_ci			    "A3000 SCSI", instance);
26262306a36Sopenharmony_ci	if (error)
26362306a36Sopenharmony_ci		goto fail_irq;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	regs->CNTR = CNTR_PDMD | CNTR_INTEN;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	error = scsi_add_host(instance, NULL);
26862306a36Sopenharmony_ci	if (error)
26962306a36Sopenharmony_ci		goto fail_host;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	platform_set_drvdata(pdev, instance);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	scsi_scan_host(instance);
27462306a36Sopenharmony_ci	return 0;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_cifail_host:
27762306a36Sopenharmony_ci	free_irq(IRQ_AMIGA_PORTS, instance);
27862306a36Sopenharmony_cifail_irq:
27962306a36Sopenharmony_ci	scsi_host_put(instance);
28062306a36Sopenharmony_cifail_alloc:
28162306a36Sopenharmony_ci	release_mem_region(res->start, resource_size(res));
28262306a36Sopenharmony_ci	return error;
28362306a36Sopenharmony_ci}
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_cistatic int __exit amiga_a3000_scsi_remove(struct platform_device *pdev)
28662306a36Sopenharmony_ci{
28762306a36Sopenharmony_ci	struct Scsi_Host *instance = platform_get_drvdata(pdev);
28862306a36Sopenharmony_ci	struct a3000_hostdata *hdata = shost_priv(instance);
28962306a36Sopenharmony_ci	struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
29062306a36Sopenharmony_ci
29162306a36Sopenharmony_ci	hdata->regs->CNTR = 0;
29262306a36Sopenharmony_ci	scsi_remove_host(instance);
29362306a36Sopenharmony_ci	free_irq(IRQ_AMIGA_PORTS, instance);
29462306a36Sopenharmony_ci	scsi_host_put(instance);
29562306a36Sopenharmony_ci	release_mem_region(res->start, resource_size(res));
29662306a36Sopenharmony_ci	return 0;
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_cistatic struct platform_driver amiga_a3000_scsi_driver = {
30062306a36Sopenharmony_ci	.remove = __exit_p(amiga_a3000_scsi_remove),
30162306a36Sopenharmony_ci	.driver   = {
30262306a36Sopenharmony_ci		.name	= "amiga-a3000-scsi",
30362306a36Sopenharmony_ci	},
30462306a36Sopenharmony_ci};
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_cimodule_platform_driver_probe(amiga_a3000_scsi_driver, amiga_a3000_scsi_probe);
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ciMODULE_DESCRIPTION("Amiga 3000 built-in SCSI");
30962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
31062306a36Sopenharmony_ciMODULE_ALIAS("platform:amiga-a3000-scsi");
311