414 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			414 lines
		
	
	
		
			9.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| /*
 | |
|  *	Adaptec AAC series RAID controller driver
 | |
|  *	(c) Copyright 2001 Red Hat Inc.
 | |
|  *
 | |
|  * based on the old aacraid driver that is..
 | |
|  * Adaptec aacraid device driver for Linux.
 | |
|  *
 | |
|  * Copyright (c) 2000-2010 Adaptec, Inc.
 | |
|  *               2010-2015 PMC-Sierra, Inc. (aacraid@pmc-sierra.com)
 | |
|  *		 2016-2017 Microsemi Corp. (aacraid@microsemi.com)
 | |
|  *
 | |
|  * Module Name:
 | |
|  *  sa.c
 | |
|  *
 | |
|  * Abstract: Drawbridge specific support functions
 | |
|  */
 | |
| 
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/init.h>
 | |
| #include <linux/types.h>
 | |
| #include <linux/pci.h>
 | |
| #include <linux/spinlock.h>
 | |
| #include <linux/blkdev.h>
 | |
| #include <linux/delay.h>
 | |
| #include <linux/completion.h>
 | |
| #include <linux/time.h>
 | |
| #include <linux/interrupt.h>
 | |
| 
 | |
| #include <scsi/scsi_host.h>
 | |
| 
 | |
| #include "aacraid.h"
 | |
| 
 | |
| static irqreturn_t aac_sa_intr(int irq, void *dev_id)
 | |
| {
 | |
| 	struct aac_dev *dev = dev_id;
 | |
| 	unsigned short intstat, mask;
 | |
| 
 | |
| 	intstat = sa_readw(dev, DoorbellReg_p);
 | |
| 	/*
 | |
| 	 *	Read mask and invert because drawbridge is reversed.
 | |
| 	 *	This allows us to only service interrupts that have been enabled.
 | |
| 	 */
 | |
| 	mask = ~(sa_readw(dev, SaDbCSR.PRISETIRQMASK));
 | |
| 
 | |
| 	/* Check to see if this is our interrupt.  If it isn't just return */
 | |
| 
 | |
| 	if (intstat & mask) {
 | |
| 		if (intstat & PrintfReady) {
 | |
| 			aac_printf(dev, sa_readl(dev, Mailbox5));
 | |
| 			sa_writew(dev, DoorbellClrReg_p, PrintfReady); /* clear PrintfReady */
 | |
| 			sa_writew(dev, DoorbellReg_s, PrintfDone);
 | |
| 		} else if (intstat & DOORBELL_1) {	// dev -> Host Normal Command Ready
 | |
| 			sa_writew(dev, DoorbellClrReg_p, DOORBELL_1);
 | |
| 			aac_command_normal(&dev->queues->queue[HostNormCmdQueue]);
 | |
| 		} else if (intstat & DOORBELL_2) {	// dev -> Host Normal Response Ready
 | |
| 			sa_writew(dev, DoorbellClrReg_p, DOORBELL_2);
 | |
| 			aac_response_normal(&dev->queues->queue[HostNormRespQueue]);
 | |
| 		} else if (intstat & DOORBELL_3) {	// dev -> Host Normal Command Not Full
 | |
| 			sa_writew(dev, DoorbellClrReg_p, DOORBELL_3);
 | |
| 		} else if (intstat & DOORBELL_4) {	// dev -> Host Normal Response Not Full
 | |
| 			sa_writew(dev, DoorbellClrReg_p, DOORBELL_4);
 | |
| 		}
 | |
| 		return IRQ_HANDLED;
 | |
| 	}
 | |
| 	return IRQ_NONE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  *	aac_sa_disable_interrupt	-	disable interrupt
 | |
|  *	@dev: Which adapter to enable.
 | |
|  */
 | |
| 
 | |
| static void aac_sa_disable_interrupt (struct aac_dev *dev)
 | |
| {
 | |
| 	sa_writew(dev, SaDbCSR.PRISETIRQMASK, 0xffff);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  *	aac_sa_enable_interrupt	-	enable interrupt
 | |
|  *	@dev: Which adapter to enable.
 | |
|  */
 | |
| 
 | |
| static void aac_sa_enable_interrupt (struct aac_dev *dev)
 | |
| {
 | |
| 	sa_writew(dev, SaDbCSR.PRICLEARIRQMASK, (PrintfReady | DOORBELL_1 |
 | |
| 				DOORBELL_2 | DOORBELL_3 | DOORBELL_4));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  *	aac_sa_notify_adapter		-	handle adapter notification
 | |
|  *	@dev:	Adapter that notification is for
 | |
|  *	@event:	Event to notidy
 | |
|  *
 | |
|  *	Notify the adapter of an event
 | |
|  */
 | |
|  
 | |
| static void aac_sa_notify_adapter(struct aac_dev *dev, u32 event)
 | |
| {
 | |
| 	switch (event) {
 | |
| 
 | |
| 	case AdapNormCmdQue:
 | |
| 		sa_writew(dev, DoorbellReg_s,DOORBELL_1);
 | |
| 		break;
 | |
| 	case HostNormRespNotFull:
 | |
| 		sa_writew(dev, DoorbellReg_s,DOORBELL_4);
 | |
| 		break;
 | |
| 	case AdapNormRespQue:
 | |
| 		sa_writew(dev, DoorbellReg_s,DOORBELL_2);
 | |
| 		break;
 | |
| 	case HostNormCmdNotFull:
 | |
| 		sa_writew(dev, DoorbellReg_s,DOORBELL_3);
 | |
| 		break;
 | |
| 	case HostShutdown:
 | |
| 		/*
 | |
| 		sa_sync_cmd(dev, HOST_CRASHING, 0, 0, 0, 0, 0, 0,
 | |
| 		NULL, NULL, NULL, NULL, NULL);
 | |
| 		*/
 | |
| 		break;
 | |
| 	case FastIo:
 | |
| 		sa_writew(dev, DoorbellReg_s,DOORBELL_6);
 | |
| 		break;
 | |
| 	case AdapPrintfDone:
 | |
| 		sa_writew(dev, DoorbellReg_s,DOORBELL_5);
 | |
| 		break;
 | |
| 	default:
 | |
| 		BUG();
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  *	sa_sync_cmd	-	send a command and wait
 | |
|  *	@dev: Adapter
 | |
|  *	@command: Command to execute
 | |
|  *	@p1: first parameter
 | |
|  *	@p2: second parameter
 | |
|  *	@p3: third parameter
 | |
|  *	@p4: forth parameter
 | |
|  *	@p5: fifth parameter
 | |
|  *	@p6: sixth parameter
 | |
|  *	@ret: adapter status
 | |
|  *	@r1: first return value
 | |
|  *	@r2: second return value
 | |
|  *	@r3: third return value
 | |
|  *	@r4: forth return value
 | |
|  *
 | |
|  *	This routine will send a synchronous command to the adapter and wait
 | |
|  *	for its	completion.
 | |
|  */
 | |
| static int sa_sync_cmd(struct aac_dev *dev, u32 command,
 | |
| 		u32 p1, u32 p2, u32 p3, u32 p4, u32 p5, u32 p6,
 | |
| 		u32 *ret, u32 *r1, u32 *r2, u32 *r3, u32 *r4)
 | |
| {
 | |
| 	unsigned long start;
 | |
|  	int ok;
 | |
| 	/*
 | |
| 	 *	Write the Command into Mailbox 0
 | |
| 	 */
 | |
| 	sa_writel(dev, Mailbox0, command);
 | |
| 	/*
 | |
| 	 *	Write the parameters into Mailboxes 1 - 4
 | |
| 	 */
 | |
| 	sa_writel(dev, Mailbox1, p1);
 | |
| 	sa_writel(dev, Mailbox2, p2);
 | |
| 	sa_writel(dev, Mailbox3, p3);
 | |
| 	sa_writel(dev, Mailbox4, p4);
 | |
| 
 | |
| 	/*
 | |
| 	 *	Clear the synch command doorbell to start on a clean slate.
 | |
| 	 */
 | |
| 	sa_writew(dev, DoorbellClrReg_p, DOORBELL_0);
 | |
| 	/*
 | |
| 	 *	Signal that there is a new synch command
 | |
| 	 */
 | |
| 	sa_writew(dev, DoorbellReg_s, DOORBELL_0);
 | |
| 
 | |
| 	ok = 0;
 | |
| 	start = jiffies;
 | |
| 
 | |
| 	while(time_before(jiffies, start+30*HZ))
 | |
| 	{
 | |
| 		/*
 | |
| 		 *	Delay 5uS so that the monitor gets access
 | |
| 		 */
 | |
| 		udelay(5);
 | |
| 		/*
 | |
| 		 *	Mon110 will set doorbell0 bit when it has 
 | |
| 		 *	completed the command.
 | |
| 		 */
 | |
| 		if(sa_readw(dev, DoorbellReg_p) & DOORBELL_0)  {
 | |
| 			ok = 1;
 | |
| 			break;
 | |
| 		}
 | |
| 		msleep(1);
 | |
| 	}
 | |
| 
 | |
| 	if (ok != 1)
 | |
| 		return -ETIMEDOUT;
 | |
| 	/*
 | |
| 	 *	Clear the synch command doorbell.
 | |
| 	 */
 | |
| 	sa_writew(dev, DoorbellClrReg_p, DOORBELL_0);
 | |
| 	/*
 | |
| 	 *	Pull the synch status from Mailbox 0.
 | |
| 	 */
 | |
| 	if (ret)
 | |
| 		*ret = sa_readl(dev, Mailbox0);
 | |
| 	if (r1)
 | |
| 		*r1 = sa_readl(dev, Mailbox1);
 | |
| 	if (r2)
 | |
| 		*r2 = sa_readl(dev, Mailbox2);
 | |
| 	if (r3)
 | |
| 		*r3 = sa_readl(dev, Mailbox3);
 | |
| 	if (r4)
 | |
| 		*r4 = sa_readl(dev, Mailbox4);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  *	aac_sa_interrupt_adapter	-	interrupt an adapter
 | |
|  *	@dev: Which adapter to enable.
 | |
|  *
 | |
|  *	Breakpoint an adapter.
 | |
|  */
 | |
|  
 | |
| static void aac_sa_interrupt_adapter (struct aac_dev *dev)
 | |
| {
 | |
| 	sa_sync_cmd(dev, BREAKPOINT_REQUEST, 0, 0, 0, 0, 0, 0,
 | |
| 			NULL, NULL, NULL, NULL, NULL);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  *	aac_sa_start_adapter		-	activate adapter
 | |
|  *	@dev:	Adapter
 | |
|  *
 | |
|  *	Start up processing on an ARM based AAC adapter
 | |
|  */
 | |
| 
 | |
| static void aac_sa_start_adapter(struct aac_dev *dev)
 | |
| {
 | |
| 	union aac_init *init;
 | |
| 	/*
 | |
| 	 * Fill in the remaining pieces of the init.
 | |
| 	 */
 | |
| 	init = dev->init;
 | |
| 	init->r7.host_elapsed_seconds = cpu_to_le32(ktime_get_real_seconds());
 | |
| 	/* We can only use a 32 bit address here */
 | |
| 	sa_sync_cmd(dev, INIT_STRUCT_BASE_ADDRESS, 
 | |
| 			(u32)(ulong)dev->init_pa, 0, 0, 0, 0, 0,
 | |
| 			NULL, NULL, NULL, NULL, NULL);
 | |
| }
 | |
| 
 | |
| static int aac_sa_restart_adapter(struct aac_dev *dev, int bled, u8 reset_type)
 | |
| {
 | |
| 	return -EINVAL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  *	aac_sa_check_health
 | |
|  *	@dev: device to check if healthy
 | |
|  *
 | |
|  *	Will attempt to determine if the specified adapter is alive and
 | |
|  *	capable of handling requests, returning 0 if alive.
 | |
|  */
 | |
| static int aac_sa_check_health(struct aac_dev *dev)
 | |
| {
 | |
| 	long status = sa_readl(dev, Mailbox7);
 | |
| 
 | |
| 	/*
 | |
| 	 *	Check to see if the board failed any self tests.
 | |
| 	 */
 | |
| 	if (status & SELF_TEST_FAILED)
 | |
| 		return -1;
 | |
| 	/*
 | |
| 	 *	Check to see if the board panic'd while booting.
 | |
| 	 */
 | |
| 	if (status & KERNEL_PANIC)
 | |
| 		return -2;
 | |
| 	/*
 | |
| 	 *	Wait for the adapter to be up and running. Wait up to 3 minutes
 | |
| 	 */
 | |
| 	if (!(status & KERNEL_UP_AND_RUNNING))
 | |
| 		return -3;
 | |
| 	/*
 | |
| 	 *	Everything is OK
 | |
| 	 */
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  *	aac_sa_ioremap
 | |
|  *	@dev: device to ioremap
 | |
|  *	@size: mapping resize request
 | |
|  *
 | |
|  */
 | |
| static int aac_sa_ioremap(struct aac_dev * dev, u32 size)
 | |
| {
 | |
| 	if (!size) {
 | |
| 		iounmap(dev->regs.sa);
 | |
| 		return 0;
 | |
| 	}
 | |
| 	dev->base = dev->regs.sa = ioremap(dev->base_start, size);
 | |
| 	return (dev->base == NULL) ? -1 : 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  *	aac_sa_init	-	initialize an ARM based AAC card
 | |
|  *	@dev: device to configure
 | |
|  *
 | |
|  *	Allocate and set up resources for the ARM based AAC variants. The
 | |
|  *	device_interface in the commregion will be allocated and linked
 | |
|  *	to the comm region.
 | |
|  */
 | |
| 
 | |
| int aac_sa_init(struct aac_dev *dev)
 | |
| {
 | |
| 	unsigned long start;
 | |
| 	unsigned long status;
 | |
| 	int instance;
 | |
| 	const char *name;
 | |
| 
 | |
| 	instance = dev->id;
 | |
| 	name     = dev->name;
 | |
| 
 | |
| 	/*
 | |
| 	 *	Fill in the function dispatch table.
 | |
| 	 */
 | |
| 
 | |
| 	dev->a_ops.adapter_interrupt = aac_sa_interrupt_adapter;
 | |
| 	dev->a_ops.adapter_disable_int = aac_sa_disable_interrupt;
 | |
| 	dev->a_ops.adapter_enable_int = aac_sa_enable_interrupt;
 | |
| 	dev->a_ops.adapter_notify = aac_sa_notify_adapter;
 | |
| 	dev->a_ops.adapter_sync_cmd = sa_sync_cmd;
 | |
| 	dev->a_ops.adapter_check_health = aac_sa_check_health;
 | |
| 	dev->a_ops.adapter_restart = aac_sa_restart_adapter;
 | |
| 	dev->a_ops.adapter_start = aac_sa_start_adapter;
 | |
| 	dev->a_ops.adapter_intr = aac_sa_intr;
 | |
| 	dev->a_ops.adapter_deliver = aac_rx_deliver_producer;
 | |
| 	dev->a_ops.adapter_ioremap = aac_sa_ioremap;
 | |
| 
 | |
| 	if (aac_sa_ioremap(dev, dev->base_size)) {
 | |
| 		printk(KERN_WARNING "%s: unable to map adapter.\n", name);
 | |
| 		goto error_iounmap;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 *	Check to see if the board failed any self tests.
 | |
| 	 */
 | |
| 	if (sa_readl(dev, Mailbox7) & SELF_TEST_FAILED) {
 | |
| 		printk(KERN_WARNING "%s%d: adapter self-test failed.\n", name, instance);
 | |
| 		goto error_iounmap;
 | |
| 	}
 | |
| 	/*
 | |
| 	 *	Check to see if the board panic'd while booting.
 | |
| 	 */
 | |
| 	if (sa_readl(dev, Mailbox7) & KERNEL_PANIC) {
 | |
| 		printk(KERN_WARNING "%s%d: adapter kernel panic'd.\n", name, instance);
 | |
| 		goto error_iounmap;
 | |
| 	}
 | |
| 	start = jiffies;
 | |
| 	/*
 | |
| 	 *	Wait for the adapter to be up and running. Wait up to 3 minutes.
 | |
| 	 */
 | |
| 	while (!(sa_readl(dev, Mailbox7) & KERNEL_UP_AND_RUNNING)) {
 | |
| 		if (time_after(jiffies, start+startup_timeout*HZ)) {
 | |
| 			status = sa_readl(dev, Mailbox7);
 | |
| 			printk(KERN_WARNING "%s%d: adapter kernel failed to start, init status = %lx.\n", 
 | |
| 					name, instance, status);
 | |
| 			goto error_iounmap;
 | |
| 		}
 | |
| 		msleep(1);
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 *	First clear out all interrupts.  Then enable the one's that 
 | |
| 	 *	we can handle.
 | |
| 	 */
 | |
| 	aac_adapter_disable_int(dev);
 | |
| 	aac_adapter_enable_int(dev);
 | |
| 
 | |
| 	if(aac_init_adapter(dev) == NULL)
 | |
| 		goto error_irq;
 | |
| 	dev->sync_mode = 0;	/* sync. mode not supported */
 | |
| 	if (request_irq(dev->pdev->irq, dev->a_ops.adapter_intr,
 | |
| 			IRQF_SHARED, "aacraid", (void *)dev) < 0) {
 | |
| 		printk(KERN_WARNING "%s%d: Interrupt unavailable.\n",
 | |
| 			name, instance);
 | |
| 		goto error_iounmap;
 | |
| 	}
 | |
| 	dev->dbg_base = dev->base_start;
 | |
| 	dev->dbg_base_mapped = dev->base;
 | |
| 	dev->dbg_size = dev->base_size;
 | |
| 
 | |
| 	aac_adapter_enable_int(dev);
 | |
| 
 | |
| 	/*
 | |
| 	 *	Tell the adapter that all is configure, and it can start 
 | |
| 	 *	accepting requests
 | |
| 	 */
 | |
| 	aac_sa_start_adapter(dev);
 | |
| 	return 0;
 | |
| 
 | |
| error_irq:
 | |
| 	aac_sa_disable_interrupt(dev);
 | |
| 	free_irq(dev->pdev->irq, (void *)dev);
 | |
| 
 | |
| error_iounmap:
 | |
| 
 | |
| 	return -1;
 | |
| }
 | |
| 
 |