All of lore.kernel.org
 help / color / mirror / Atom feed
From: Luben Tuikov <luben_tuikov@adaptec.com>
To: SCSI Mailing List <linux-scsi@vger.kernel.org>
Subject: [ANNOUNCE] Adaptec SAS/SATA device driver [09/27]
Date: Thu, 17 Feb 2005 12:36:19 -0500	[thread overview]
Message-ID: <4214D613.2000006@adaptec.com> (raw)

Hardware interface. Part 3/3.

+
+static void
+asd_hwi_reset_device_done(struct asd_softc *asd, struct scb *scb,
+			  struct asd_done_list *dl)
+{
+	asd_log(ASD_DBG_ERROR, "DL Opcode = 0x%x.\n", dl->opcode);
+
+	/*
+       	 * There is a possibility that this post routine is called after
+	 * the SCB timedout. So, only delete the timer if the SCB hasn't
+	 * timedout.
+	 */
+	if (scb->eh_state != SCB_EH_TIMEDOUT)
+		del_timer_sync(&scb->platform_data->timeout);
+
+	switch (dl->opcode) {
+	case TASK_COMP_WO_ERR:
+	{	
+		if (((scb->eh_state & SCB_EH_INITIATED) != 0) ||
+		    ((scb->eh_state & SCB_EH_PHY_REPORT_REQ) != 0)) {
+			struct asd_port	*port;
+		
+			port = SCB_GET_SRC_PORT(scb);
+			if (port->management_type == ASD_DEVICE_END) {
+				/*
+		 	 	 * For direct-attached SSP target, we need to
+				 * check if it is attached on a wide port. 
+				 * If so, we need to issue a HARD RESET on 
+				 * one of the phys and LINK RESET on the 
+				 * remaining phys.
+			 	 * For direct-attached SATA/SATAPI target,
+				 * LINK RESET can be done for all the phys
+				 * belong to the port.
+				 * Get which phy(s) that need to be reset.
+		 	 	 */
+				port->reset_mask = port->conn_mask;
+			}
+			scb->eh_state = SCB_EH_DEV_RESET_REQ;
+		} else {
+			scb->eh_state = SCB_EH_DONE;
+		}
+		scb->eh_status = SCB_EH_SUCCEED;
+		break;
+	}
+
+	case TMF_F_W_TC_NOT_FOUND:
+	case TMF_F_W_TAG_NOT_FOUND:
+	case TMF_F_W_CONN_HNDL_NOT_FOUND:
+		scb->eh_state = SCB_EH_DONE;
+		scb->eh_status = SCB_EH_FAILED;
+		break;
+
+	case TASK_CLEARED:
+		/* 
+		 * Previous error recovery SCB that timed-out and
+		 * was aborted.
+		 * All we need to do here is just free the scb.
+		 */ 
+		asd_hwi_free_scb(asd, scb);
+		return;
+
+	default:
+		asd_log(ASD_DBG_ERROR, "Unhandled DL opcode.\n");
+		scb->eh_state = SCB_EH_DONE;
+		scb->eh_status = SCB_EH_FAILED;
+		break;
+	}
+
+	asd_hwi_reset_device(asd, (struct scb *) scb->io_ctx, scb);
+}
+
+/*
+ * Function:
+ *	asd_hwi_reset_end_device()
+ *	
+ * Description:
+ *	For direct-attached SATA/SATAPI target, device reset is achieved by 
+ *	issuing a Link Reset sequence (OOB).
+ *	For direct-attached SSP target, device reset is achieved by issuing
+ *	a Hard Reset.
+ */
+static void
+asd_hwi_reset_end_device(struct asd_softc *asd, struct scb *scb)
+{
+	struct asd_target	*targ;
+	struct asd_port		*port;
+	struct asd_phy		*phy;
+	int			 found;
+
+	targ = scb->platform_data->targ;
+	port = SCB_GET_SRC_PORT(scb);
+	found = 0;
+	/*
+	 * Find the associated phy that need to be reset.
+	 */
+	list_for_each_entry(phy, &port->phys_attached, links) {
+		if ((port->reset_mask & (1 << phy->id)) != 0) {
+			found = 1;
+			break;
+		}
+	}
+	if (found != 1) {
+		/* This shouldn't happen. */
+		asd_log(ASD_DBG_ERROR,"No PHY to reset, PR Mask: 0x%x "
+			"PC Mask: 0x%x.\n", port->reset_mask, port->conn_mask);
+		scb->eh_state = SCB_EH_DONE;
+		scb->eh_status = SCB_EH_FAILED;
+		asd_hwi_reset_device(asd,
+				    (struct scb *) scb->post_stack[0].io_ctx,
+				     scb);
+		return;
+	}
+
+	if (targ->transport_type == ASD_TRANSPORT_SSP) {
+		/* 
+	 	 * For SSP wide port target, we need to perform a
+	 	 * HARD RESET only on one of the phys and LINK RESET
+		 * on the remaining phys.
+	 	 */
+		asd_hwi_build_control_phy(scb, phy,
+					 ((port->reset_mask == port->conn_mask)
+					  ? EXECUTE_HARD_RESET : ENABLE_PHY));
+	} else {
+		/* 
+	 	 * For SATA direct-attached end device, 
+	 	 * device port reset is done by re-Enabling the phy
+	 	 * and hence initiating OOB sequence.
+	 	 */
+		asd_hwi_build_control_phy(scb, phy, ENABLE_PHY); 	
+	}
+		
+	port->reset_mask &= ~(1 << phy->id);
+	scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+	asd_setup_scb_timer(scb, (8 * HZ), asd_scb_eh_timeout);
+	asd_push_post_stack(asd, scb, (void *) phy,
+			    asd_hwi_reset_end_device_done);
+	asd_hwi_post_scb(asd, scb);
+}
+
+static void
+asd_hwi_reset_end_device_done(struct asd_softc *asd, struct scb *scb, 
+			      struct asd_done_list *dl)
+{
+	asd_log(ASD_DBG_ERROR, "DL Opcode = 0x%x.\n", dl->opcode);
+
+	/*
+       	 * There is a possibility that this post routine is called after
+	 * the SCB timedout. So, only delete the timer if the SCB hasn't
+	 * timedout.
+	 */
+	if (scb->eh_state != SCB_EH_TIMEDOUT)
+		del_timer_sync(&scb->platform_data->timeout);
+
+	if (scb->eh_status == SCB_EH_SUCCEED) {
+		if (scb->eh_state != SCB_EH_PHY_NO_OP_REQ) {
+			/* Check if any remaining phys need to be reset. */
+			scb->eh_state =
+				((SCB_GET_SRC_PORT(scb)->reset_mask == 0) ?
+				  SCB_EH_CLR_NXS_REQ : SCB_EH_DEV_RESET_REQ);
+		}
+	} else {
+		scb->eh_state = SCB_EH_DONE;
+		scb->eh_status = SCB_EH_FAILED;
+	}
+		
+	asd_hwi_reset_device(asd, (struct scb *) scb->post_stack[0].io_ctx,
+			     scb);
+}
+
+static void
+asd_hwi_report_phy_err_log(struct asd_softc *asd, struct scb *scb)
+{
+	struct asd_port		*port;
+	struct asd_target	*exp_targ;
+	struct asd_target	*targ;
+	struct Discover		*disc;
+	int			 phy_id;
+
+	targ = scb->platform_data->targ;
+	/* Expander that the target is attached to. */
+	exp_targ = targ->parent;
+	port = SCB_GET_SRC_PORT(scb);
+
+	if (exp_targ == NULL) {
+		/* This shouldn't happen. */
+		asd_log(ASD_DBG_ERROR, "Parent Expander shouldn't be NULL.\n");
+		scb->eh_state = SCB_EH_DONE;
+		scb->eh_status = SCB_EH_FAILED;
+		asd_hwi_reset_device(asd,
+				    (struct scb *) scb->post_stack[0].io_ctx,
+				     scb);
+		return;
+	}
+	/*
+	 * Find the expander phy id that the target is attached to. 
+	 */
+	for (phy_id = 0; phy_id < exp_targ->num_phys; phy_id++) {
+		disc = &(exp_targ->Phy[phy_id].Result);
+
+		if (SAS_ISEQUAL(targ->ddb_profile.sas_addr,
+				disc->AttachedSASAddress))
+			break;
+	}
+
+	if (phy_id == exp_targ->num_phys) {
+		/* This shouldn't happen. */
+		asd_log(ASD_DBG_ERROR, "Corrupted target, inv. phy id.\n");
+		scb->eh_state = SCB_EH_DONE;
+		scb->eh_status = SCB_EH_FAILED;
+		asd_hwi_reset_device(asd,
+				    (struct scb *) scb->post_stack[0].io_ctx,
+				     scb);
+		return;
+	}
+
+	/* Build a REPORT PHY ERROR LOG SMP request. */
+	asd_hwi_build_smp_phy_req(port, REPORT_PHY_ERROR_LOG, phy_id, 0);
+
+	/* Build a SMP TASK. */
+	asd_hwi_build_smp_task(scb, exp_targ,
+			       port->dc.SMPRequestBusAddr,
+			       sizeof(struct SMPRequestPhyInput),
+			       port->dc.SMPResponseBusAddr,
+			       sizeof(struct SMPResponseReportPhyErrorLog));
+
+	scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+	asd_setup_scb_timer(scb, (4 * HZ), asd_scb_eh_timeout);
+	asd_push_post_stack(asd, scb, (void *) port,
+			    asd_hwi_reset_exp_device_done);
+	asd_hwi_post_scb(asd, scb);
+}
+
+/*
+ * Function:
+ *	asd_hwi_reset_exp_device()
+ *	
+ * Description:
+ * 	For device that is attached behind an expander device, device reset
+ *	is achieved by issuing SMP PHY CONTROL with a phy operation of:
+ *	- HARD RESET for SSP and STP device port.
+ *	- LINK RESET for SATA/SATAPI device port.
+ */
+static void
+asd_hwi_reset_exp_device(struct asd_softc *asd, struct scb *scb)
+{
+	struct asd_target	*exp_targ;
+	struct asd_target	*targ;
+	struct asd_port		*port;
+	struct Discover		*disc;
+	int			 phy_id;
+
+	port = SCB_GET_SRC_PORT(scb);
+	targ = scb->platform_data->targ;
+	/* Expander that the target is attached to. */
+	exp_targ = targ->parent;
+	if (exp_targ == NULL) {
+		/* This shouldn't happen. */
+		asd_log(ASD_DBG_ERROR, "Parent Expander shouldn't be NULL.\n");
+		scb->eh_state = SCB_EH_DONE;
+		scb->eh_status = SCB_EH_FAILED;
+		asd_hwi_reset_device(asd,
+				    (struct scb *) scb->post_stack[0].io_ctx,
+				     scb);
+		return;
+	}
+
+	/*
+	 * Find the expander phy id that the target is attached to. 
+	 */
+	for (phy_id = 0; phy_id < exp_targ->num_phys; phy_id++) {
+		disc = &(exp_targ->Phy[phy_id].Result);
+
+		if (SAS_ISEQUAL(targ->ddb_profile.sas_addr,
+				disc->AttachedSASAddress))
+			break;
+	}
+
+	if (phy_id == exp_targ->num_phys) {
+		/* This shouldn't happen. */
+		asd_log(ASD_DBG_ERROR, "Corrupted target, inv. phy id.\n");
+		scb->eh_state = SCB_EH_DONE;
+		scb->eh_status = SCB_EH_FAILED;
+		asd_hwi_reset_device(asd,
+				    (struct scb *) scb->post_stack[0].io_ctx,
+				     scb);
+		return;
+	}
+
+	/* 
+	 * For SSP/STP target port, CONTROL PHY-HARD RESET will be issued.
+	 * For SATA/SATAPI target port, CONTROL PHY-LINK RESET will be issued.
+	 */
+	asd_hwi_build_smp_phy_req(
+		port, PHY_CONTROL, phy_id,
+		((targ->transport_type == ASD_TRANSPORT_ATA) ? LINK_RESET :
+		 					       HARD_RESET));
+
+	/* Build a SMP REQUEST. */
+	asd_hwi_build_smp_task(scb, exp_targ,
+			       port->dc.SMPRequestBusAddr,
+			       sizeof(struct SMPRequestPhyControl),
+			       port->dc.SMPResponseBusAddr,
+			       sizeof(struct SMPResponsePhyControl));
+
+	scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+	asd_setup_scb_timer(scb, (8 * HZ), asd_scb_eh_timeout);
+	asd_push_post_stack(asd, scb, (void *) port,
+			    asd_hwi_reset_exp_device_done);
+	asd_hwi_post_scb(asd, scb);
+}
+
+static void
+asd_hwi_reset_exp_device_done(struct asd_softc *asd, struct scb *scb, 
+			      struct asd_done_list *dl)
+{
+	asd_log(ASD_DBG_ERROR, "DL Opcode = 0x%x.\n", dl->opcode);
+
+	/*
+       	 * There is a possibility that this post routine is called after
+	 * the SCB timedout. So, only delete the timer if the SCB hasn't
+	 * timedout.
+	 */
+	if (scb->eh_state != SCB_EH_TIMEDOUT)
+		del_timer_sync(&scb->platform_data->timeout);
+
+	switch (dl->opcode) {
+	case TASK_COMP_WO_ERR:
+	{	
+		struct asd_port	*port;
+
+		port = (struct asd_port *) scb->io_ctx;
+		/* Check the SMP Response function result. */
+		if (port->dc.SMPResponseFrame->FunctionResult ==
+			SMP_FUNCTION_ACCEPTED) {
+			if (scb->eh_state == SCB_EH_PHY_REPORT_REQ) {
+				asd_hwi_dump_phy_err_log(port, scb);
+				scb->eh_state = SCB_EH_DONE;
+			} else {
+				scb->eh_state = SCB_EH_CLR_NXS_REQ;
+			}
+			scb->eh_status = SCB_EH_SUCCEED;
+		} else {
+			scb->eh_state = SCB_EH_DONE;
+			scb->eh_status = SCB_EH_FAILED;
+		}
+		break;
+	}
+
+	case TASK_CLEARED:
+		/* 
+		 * Previous error recovery SCB that timed-out and
+		 * was aborted.
+		 * All we need to do here is just free the scb.
+		 */ 
+		asd_hwi_free_scb(asd, scb);
+		return;
+
+	case TASK_F_W_SMPRSP_TO:
+	case TASK_F_W_SMP_XMTRCV_ERR:
+	default:
+		scb->eh_state = SCB_EH_DONE;
+		scb->eh_status = SCB_EH_FAILED;
+		break;
+	}
+
+	asd_hwi_reset_device(asd, (struct scb *) scb->post_stack[0].io_ctx,
+			     scb);
+}
+
+static void
+asd_hwi_dump_phy_err_log(struct asd_port *port, struct scb *scb)
+{
+	struct SMPResponseReportPhyErrorLog 	*report_phy_log;
+	struct asd_target			*targ;
+	struct asd_device			*dev;
+
+	report_phy_log = (struct SMPResponseReportPhyErrorLog *)
+				&(port->dc.SMPResponseFrame->Response);
+	targ = scb->platform_data->targ;
+	dev = scb->platform_data->dev;
+
+	asd_print("REPORT PHY ERROR LOG\n");
+	asd_print("---------------------\n");
+	asd_print("Phy #%d of Expander 0x%llx.\n",
+		  report_phy_log->PhyIdentifier, asd_be64toh(
+		  *((uint64_t *) targ->parent->ddb_profile.sas_addr)));
+	asd_print("Attached device:\n"); 
+	asd_print("Scsi %d Ch %d Tgt %d Lun %d, SAS Addr: 0x%llx.\n",
+		  targ->softc->platform_data->scsi_host->host_no,
+		  dev->ch, dev->id, dev->lun, asd_be64toh(
+		  *((uint64_t *) targ->ddb_profile.sas_addr)));
+	asd_print("\nPHY ERROR COUNTS\n");
+	asd_print("----------------\n");
+	asd_print("Invalid Dword Count: %d.\n",
+		  report_phy_log->InvalidDuint16_tCount);
+	asd_print("Disparity Error Count: %d.\n",
+		  report_phy_log->DisparityErrorCount);
+	asd_print("Loss of Dword Synchronization Count: %d.\n",
+		  report_phy_log->LossOfDuint16_tSynchronizationCount);
+	asd_print("Phy Reset Problem Count: %d.\n",
+		  report_phy_log->PhyResetProblemCount);
+}
+
+/*
+ * Function:
+ *	asd_hwi_reset_port()
+ *	
+ * Description:
+ *	Issue a HARD/LINK RESET to the failing port.
+ */
+static void
+asd_hwi_reset_port(struct asd_softc *asd, struct scb *scb_to_reset,
+		   struct scb *scb)
+{
+	ASD_LOCK_ASSERT(asd);
+
+	asd_log(ASD_DBG_ERROR, "Curr State: 0x%x Status: 0x%x.\n",
+		scb->eh_state, scb->eh_status);
+
+	switch (scb->eh_state) {
+	case SCB_EH_INITIATED:
+	{
+		struct asd_port		*port;
+		struct asd_target	*targ;
+		
+		port = SCB_GET_SRC_PORT(scb_to_reset);
+		if (port == NULL) {
+			asd_log(ASD_DBG_ERROR," Invalid port to reset.\n");
+			scb->eh_state = SCB_EH_DONE;
+			scb->eh_state = SCB_EH_FAILED;
+			asd_hwi_reset_port(asd, scb_to_reset, scb);
+			break;
+		}
+		/* 
+		 * Freeze all the targets' queue attached to the port that
+		 * we are about to reset.
+		 */
+		list_for_each_entry(targ, &port->targets, all_domain_targets) {
+			targ->qfrozen++;
+		}
+
+		/* 
+		 * Saving final post routine and the scb_to_reset in
+		 * the first post stack slot. We need to access the
+		 * scb_to_reset during the device reset process.
+		 */
+		asd_push_post_stack(asd, scb, (void *) scb_to_reset,
+				    asd_hwi_reset_port_done);
+		
+		/* Bitmask of the phys to perform reset. */
+		port->reset_mask = port->conn_mask;
+		scb->platform_data->targ = scb_to_reset->platform_data->targ;
+		scb->platform_data->dev = scb_to_reset->platform_data->dev;
+		scb->eh_state = SCB_EH_CLR_NXS_REQ;
+		asd_hwi_reset_port(asd, scb_to_reset, scb);
+		break;
+	}
+	
+	case SCB_EH_CLR_NXS_REQ:
+		asd_hwi_build_clear_nexus(scb, CLR_NXS_I_OR_T,
+				  	  /*parm*/0, /*ctx*/0);
+		asd_push_post_stack(asd, scb, (void *) scb_to_reset,
+				    asd_hwi_reset_port_done);
+		scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+		asd_setup_scb_timer(scb, (4 * HZ), asd_scb_eh_timeout);
+		asd_hwi_post_scb(asd, scb);
+		break;
+
+	case SCB_EH_PORT_RESET_REQ:
+	{
+		struct asd_port	*port;
+		struct asd_phy	*phy;
+		int		 found;
+
+		found = 0;
+		port = SCB_GET_SRC_PORT(scb_to_reset);
+		list_for_each_entry(phy, &port->phys_attached, links) {
+			if ((port->reset_mask & (1 << phy->id)) != 0) {
+				found = 1;
+				break;
+			}
+		}
+		if (found != 1) {
+			/* This shouldn't happen. */
+			asd_log(ASD_DBG_ERROR,"No PHY to reset, PR Mask: 0x%x "
+				"PC Mask: 0x%x.\n",
+				port->reset_mask, port->conn_mask);
+			scb->eh_state = SCB_EH_DONE;
+			scb->eh_status = SCB_EH_FAILED;
+			asd_hwi_reset_port(asd, scb_to_reset, scb);
+			break;
+		}
+		/* 
+	 	 * For SSP initiator wide port, we need to perform a
+		 * HARD RESET only on one of the phys and LINK RESET on
+		 * the remaining phys.
+	 	 */
+		asd_hwi_build_control_phy(scb, phy,
+					 ((port->reset_mask == port->conn_mask)
+					  ? EXECUTE_HARD_RESET : ENABLE_PHY));
+
+		port->reset_mask &= ~(1 << phy->id);
+		scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+		asd_setup_scb_timer(scb, (8 * HZ), asd_scb_eh_timeout);
+		asd_push_post_stack(asd, scb, (void *) phy,
+				    asd_hwi_reset_port_done);
+		asd_hwi_post_scb(asd, scb);
+		break;
+	}
+
+	case SCB_EH_PHY_NO_OP_REQ:
+	{
+		struct asd_phy	*phy;
+
+		phy = (struct asd_phy *) scb->io_ctx;
+		/*
+		 * Upon completion of HARD RESET, we need to initialize
+		 * OOB registers to enable hot-plug timer.
+		 */
+		asd_hwi_build_control_phy(scb, phy, PHY_NO_OP);
+		scb->flags |= (SCB_INTERNAL | SCB_ACTIVE | SCB_RECOVERY);
+		asd_setup_scb_timer(scb, (4 * HZ), asd_scb_eh_timeout);
+		asd_push_post_stack(asd, scb, scb->io_ctx,
+				    asd_hwi_reset_port_done);
+		asd_hwi_post_scb(asd, scb);
+		break;
+	}
+
+	case SCB_EH_DONE:
+	{
+		struct asd_port		*port;
+		struct asd_target	*targ;
+
+		port = SCB_GET_SRC_PORT(scb_to_reset);
+		
+		/* Unfreeze all the targets. */
+		list_for_each_entry(targ, &port->targets, all_domain_targets) {
+			targ->qfrozen--;
+		}
+		scb_to_reset->eh_state = scb->eh_state;
+		scb_to_reset->eh_status = scb->eh_status;
+		asd_hwi_free_scb(asd, scb);
+		asd_wakeup_sem(&asd->platform_data->ehandler_sem);
+		break;
+	}
+
+	default:
+		asd_log(ASD_DBG_ERROR, "Invalid State.\n");
+		scb_to_reset->eh_state = SCB_EH_DONE;
+		scb_to_reset->eh_status = SCB_EH_FAILED;
+		asd_hwi_free_scb(asd, scb);
+		asd_wakeup_sem(&asd->platform_data->ehandler_sem);
+		break;
+	}
+}
+
+static void
+asd_hwi_reset_port_done(struct asd_softc *asd, struct scb *scb, 
+			struct asd_done_list *dl)
+{
+	asd_log(ASD_DBG_ERROR, "DL Opcode = 0x%x.\n", dl->opcode);
+
+	/*
+       	 * There is a possibility that this post routine is called after
+	 * the SCB timedout. So, only delete the timer if the SCB hasn't
+	 * timedout.
+	 */
+	if (scb->eh_state != SCB_EH_TIMEDOUT)
+		del_timer_sync(&scb->platform_data->timeout);
+
+	/* CLR NXS completion. */
+	if (dl->opcode == TASK_COMP_WO_ERR) {
+		scb->eh_state = SCB_EH_PORT_RESET_REQ;
+	} else if (scb->eh_status == SCB_EH_SUCCEED) {
+		if (scb->eh_state != SCB_EH_PHY_NO_OP_REQ) {
+			/* Check if any remaining phys need to be reset. */
+			scb->eh_state =
+				((SCB_GET_SRC_PORT(scb)->reset_mask == 0) ?
+				  SCB_EH_DONE : SCB_EH_PORT_RESET_REQ);
+		}
+	} else {
+		scb->eh_state = SCB_EH_DONE;
+		scb->eh_status = SCB_EH_FAILED;
+	}
+		
+	asd_hwi_reset_port(asd,
+			  (struct scb *) scb->post_stack[0].io_ctx,
+			   scb);
+}
+
+/*************************** NVRAM access utilities ***************************/
+
+#if NVRAM_SUPPORT
+
+
+/*
+ * Function:
+ *	asd_hwi_poll_nvram()
+ *
+ * Description:
+ *	This routine will poll for the NVRAM to be ready to accept new
+ *	command.
+ */
+static int
+asd_hwi_poll_nvram(struct asd_softc *asd)
+{
+	uint8_t	nv_data;
+	uint8_t	toggle_data;
+	int loop_cnt;
+
+	loop_cnt = 5000;
+
+	while (loop_cnt) {
+		nv_data = asd_hwi_swb_read_byte(asd, 
+						asd->hw_profile.nv_flash_bar);
+
+		toggle_data = (nv_data ^ asd_hwi_swb_read_byte(asd,
+						asd->hw_profile.nv_flash_bar));
+
+		if (toggle_data == 0) {
+			return (0);
+		} else {
+	  		if (((toggle_data == 0x04) && ((loop_cnt - 1) == 0)) ||
+			   ((toggle_data & 0x40) && (toggle_data & 0x20))) {
+				return (-1);
+			}
+		}
+	
+		loop_cnt--;
+		asd_delay(ASD_DELAY_COUNT);
+	}
+	return (-1);
+}
+
+static int
+asd_hwi_chk_write_status(struct asd_softc *asd, uint32_t sector_addr, 
+			uint8_t erase_flag) 
+{
+	uint32_t read_addr;
+	uint32_t loop_cnt;
+	uint8_t	nv_data1, nv_data2;
+	uint8_t	toggle_bit1/*, toggle_bit2*/;
+
+	/* 
+	 * Read from DQ2 requires sector address 
+	 * while it's dont care for DQ6 
+	 */
+	/* read_addr = asd->hw_profile.nv_flash_bar + sector_addr;*/
+	read_addr = asd->hw_profile.nv_flash_bar;
+	loop_cnt = 50000;
+
+	while (loop_cnt) {
+		nv_data1 = asd_hwi_swb_read_byte(asd, read_addr); 
+		nv_data2 = asd_hwi_swb_read_byte(asd, read_addr); 
+
+		toggle_bit1 = ((nv_data1 & FLASH_STATUS_BIT_MASK_DQ6)
+				 ^ (nv_data2 & FLASH_STATUS_BIT_MASK_DQ6));
+		/* toggle_bit2 = ((nv_data1 & FLASH_STATUS_BIT_MASK_DQ2) 
+				^ (nv_data2 & FLASH_STATUS_BIT_MASK_DQ2));*/
+
+		if (toggle_bit1 == 0) {
+			return (0);
+		} else {
+			if (nv_data2 & FLASH_STATUS_BIT_MASK_DQ5) {
+				nv_data1 = asd_hwi_swb_read_byte(asd, 
+								read_addr); 
+				nv_data2 = asd_hwi_swb_read_byte(asd,
+								read_addr); 
+				toggle_bit1 = 
+				((nv_data1 & FLASH_STATUS_BIT_MASK_DQ6) 
+				^ (nv_data2 & FLASH_STATUS_BIT_MASK_DQ6));
+				/*
+				toggle_bit2 = 
+				   ((nv_data1 & FLASH_STATUS_BIT_MASK_DQ2) 
+				   ^ (nv_data2 & FLASH_STATUS_BIT_MASK_DQ2));
+				*/
+				if (toggle_bit1 == 0) {
+					return 0;
+				}
+			}
+		}
+		loop_cnt--;
+	
+		/* 
+		 * ERASE is a sector-by-sector operation and requires
+		 * more time to finish while WRITE is byte-byte-byte
+		 * operation and takes lesser time to finish. 
+		 *
+		 * For some strange reason a reduced ERASE delay gives different
+		 * behaviour across different spirit boards. Hence we set
+		 * a optimum balance of 50mus for ERASE which works well
+		 * across all boards.
+		 */ 
+		if (erase_flag) {
+			asd_delay(FLASH_STATUS_ERASE_DELAY_COUNT);
+		} else {
+			asd_delay(FLASH_STATUS_WRITE_DELAY_COUNT);
+		}
+	}
+	return (-1);
+}
+/*
+ * Function:
+ *	asd_hwi_reset_nvram()
+ *
+ * Description:
+ *	Reset the NVRAM section.
+ */
+static int
+asd_hwi_reset_nvram(struct asd_softc *asd)
+{
+	/* Poll till NVRAM is ready for new command. */
+	if (asd_hwi_poll_nvram(asd) != 0)
+		return (-1);
+
+	asd_hwi_swb_write_byte(asd, asd->hw_profile.nv_flash_bar, NVRAM_RESET);
+
+	/* Poll if the command is successfully written. */
+	if (asd_hwi_poll_nvram(asd) != 0)
+		return (-1);	
+
+	return (0);
+}
+
+/*
+ * Function:
+ * 	asd_hwi_search_nv_cookie()
+ *
+ * Description:
+ *	Search the cookie in NVRAM.
+ *	If found, return the address offset of the cookie.
+ */
+int
+asd_hwi_search_nv_cookie(struct asd_softc *asd, uint32_t *addr,
+			struct asd_flash_dir_layout *pflash_dir_buf)
+{
+	struct asd_flash_dir_layout flash_dir;
+	uint8_t		 cookie_to_find[32]="*** ADAPTEC FLASH DIRECTORY *** ";
+	void		*dest_buf;
+	uint32_t	 nv_addr;
+	int		 cookie_found;
+	u_int		 bytes_read;
+
+	memset(&flash_dir, 0x0, sizeof(flash_dir));
+	dest_buf = &flash_dir;
+	cookie_found = 0;
+	nv_addr = 0;
+	while (nv_addr < NVRAM_MAX_BASE_ADR) {
+		if (asd_hwi_read_nv_segment(asd, NVRAM_NO_SEGMENT_ID,
+					    dest_buf, nv_addr,
+					    sizeof(flash_dir), 
+					    &bytes_read)
+					    != 0)
+			return (-1);
+
+		if (memcmp(flash_dir.cookie,
+			   &cookie_to_find[0],
+			   NVRAM_COOKIE_SIZE) == 0) {
+			cookie_found = 1;
+			if (pflash_dir_buf != NULL) {
+				memcpy(pflash_dir_buf, &flash_dir, 
+					sizeof(flash_dir));
+			}
+			break;
+		}
+
+		nv_addr += NVRAM_NEXT_ENTRY_OFFSET;
+	}
+	if (cookie_found == 0) {
+		return (-1);
+	}
+	
+	*addr = nv_addr;
+	asd->hw_profile.nv_cookie_addr = nv_addr;
+	asd->hw_profile.nv_cookie_found = 1;
+
+	return (0);
+}
+
+/*
+ * Function:
+ * 	asd_hwi_search_nv_segment()
+ *
+ * Description:
+ *	Search the requested NVRAM segment.
+ *	If exists, the segment offset, attributes, pad_size and image_size
+ *	will be returned.
+ */
+int
+asd_hwi_search_nv_segment(struct asd_softc *asd, u_int segment_id,
+			  uint32_t *offset, uint32_t *pad_size,
+			  uint32_t *image_size, uint32_t *attr)
+{
+	struct asd_flash_dir_layout 	flash_dir;
+	struct asd_fd_entry_layout 	fd_entry;
+	uint32_t			nv_addr;
+	int				segment_found;
+	u_int				bytes_read;
+	u_int				i;
+
+	/*
+	 * Check if we have NVRAM base addr for the FLASH directory layout.
+	 */
+	if (asd->hw_profile.nv_cookie_found != 1) {
+		if (asd_hwi_search_nv_cookie(asd, &nv_addr,
+					&flash_dir) != 0) {
+			asd_log(ASD_DBG_ERROR, "Failed to search NVRAM "
+				"cookie.\n");
+			return (-1);
+		}
+	} else {
+	    nv_addr = asd->hw_profile.nv_cookie_addr;
+	}
+		    
+	nv_addr += NVRAM_FIRST_DIR_ENTRY;
+	memset(&fd_entry, 0x0, sizeof(struct asd_fd_entry_layout));
+	segment_found = 0;
+
+	for (i = 0; i < NVRAM_MAX_ENTRIES; i++) {
+		if (asd_hwi_read_nv_segment(asd, NVRAM_NO_SEGMENT_ID,
+					&fd_entry, nv_addr,
+					sizeof(struct asd_fd_entry_layout),
+					&bytes_read) != 0) {
+			return (-1);
+		}
+
+		if ((fd_entry.attr & FD_ENTRYTYPE_CODE) == segment_id) {
+			segment_found = 1;
+			break;
+		}
+
+		nv_addr += sizeof(struct asd_fd_entry_layout);
+	}
+
+	if (segment_found == 0) {
+		return (-1);
+	}
+
+	*offset = fd_entry.offset;
+	*pad_size = fd_entry.pad_size;
+	*attr = ((fd_entry.attr & FD_SEGMENT_ATTR) ? 1 : 0);
+
+	if ((segment_id != NVRAM_CTRL_A_SETTING) &&
+		(segment_id != NVRAM_MANUF_TYPE)) {
+		*image_size = fd_entry.image_size;
+	} else {
+		*image_size = NVRAM_INVALID_SIZE;
+	}
+
+	return (0);
+}
+
+/*
+ * Function:
+ *	asd_hwi_read_nv_segment()
+ *
+ * Description:
+ *	Retrieves data from an NVRAM segment.
+ */
+int
+asd_hwi_read_nv_segment(struct asd_softc *asd, uint32_t segment_id, void *dest,
+			uint32_t src_offset, uint32_t bytes_to_read,
+			uint32_t *bytes_read)
+{
+    	uint8_t		*dest_buf;
+    	uint32_t	 nv_offset;
+	uint32_t	 pad_size, image_size, attr;
+	uint32_t	 i;
+
+	/* Reset the NVRAM. */
+	if (asd_hwi_reset_nvram(asd) != 0)
+		return (-1);
+
+	nv_offset = 0;
+	if (segment_id != NVRAM_NO_SEGMENT_ID) {
+		if (asd_hwi_search_nv_segment(asd, segment_id, &nv_offset,
+					      &pad_size, &image_size,
+					      &attr) != 0) {
+			return (-1);
+		}
+	}
+	nv_offset = asd->hw_profile.nv_flash_bar + nv_offset + src_offset;
+
+	dest_buf = (uint8_t *) dest;
+	*bytes_read = 0;
+
+	if (asd_hwi_reset_nvram(asd) != 0)
+		return (-1);
+
+	for (i = 0; i < bytes_to_read; i++) {
+		*(dest_buf + i) = asd_hwi_swb_read_byte(asd, (nv_offset + i));
+		if (asd_hwi_poll_nvram(asd) != 0) {
+			return (-1);
+		}
+	}
+	*bytes_read = i;
+
+	return (0); 
+}
+
+/*
+ * Function:
+ *	asd_hwi_write_nv_segment()
+ *
+ * Description:
+ *	Writes data to an NVRAM segment.
+ */
+int
+asd_hwi_write_nv_segment(struct asd_softc *asd, void *src, uint32_t segment_id, 
+			uint32_t dest_offset, uint32_t bytes_to_write)
+{
+    	uint8_t	*src_buf;
+	uint32_t nv_offset, nv_flash_bar, i;
+	uint32_t pad_size, image_size, attr;
+
+	nv_flash_bar = asd->hw_profile.nv_flash_bar;
+	src_buf = NULL;
+	nv_offset = 0;
+	attr = 0;
+	pad_size = 0;
+	image_size = 0;
+
+	if (asd_hwi_reset_nvram(asd) != 0) {
+		return (-1);
+	}
+	if (segment_id != NVRAM_NO_SEGMENT_ID) {
+		if (asd_hwi_search_nv_segment(asd, segment_id, &nv_offset, 
+				&pad_size, &image_size, &attr) != 0) {
+			return (-1);
+		}
+		if ((bytes_to_write > pad_size) || (dest_offset != 0)) {
+			return (-1);
+		}
+	}
+
+	nv_offset += dest_offset;
+	if (segment_id == NVRAM_NO_SEGMENT_ID 
+	   || attr == NVRAM_SEGMENT_ATTR_ERASEWRITE) {
+		if (asd_hwi_erase_nv_sector(asd, nv_offset) != 0) {
+			printk("<1>adp94xx: Erase failed at offset:0x%x\n",
+				nv_offset);
+			return (-1);
+		}
+	}
+
+	if (asd_hwi_reset_nvram(asd) != 0) {
+		return (-1);
+	}
+	src_buf = (uint8_t *)src;
+	for (i = 0; i < bytes_to_write; i++) {
+		/* Setup program command sequence */
+		switch (asd->hw_profile.flash_dev_id) {
+		case FLASH_DEV_ID_AM29LV800D:
+		{
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + 0xAAA), 0xAA);
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + 0x555), 0x55);
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + 0xAAA), 0xA0);
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + nv_offset + i),
+					(*(src_buf + i)));
+			break;
+		}
+		case FLASH_DEV_ID_AM29LV008B:
+		{
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + 0x555), 0xAA);
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + 0x2AA), 0x55);
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + 0x555), 0xA0);
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + nv_offset + i),
+					(*(src_buf + i)));
+			break;
+		}
+		default:
+			break;
+		}
+		if (asd_hwi_chk_write_status(asd, (nv_offset + i), 
+			0 /* WRITE operation */) != 0) {
+			printk("<1>adp94xx: Write failed at offset:0x%x\n",
+				nv_flash_bar + nv_offset + i);
+			return -1;
+		}
+	}
+
+#if 0
+	/* Add padding to the image. */
+	for (i = bytes_to_write; i < pad_size; i++) {
+		switch (asd->hw_profile.flash_dev_id) {
+		case FLASH_DEV_ID_AM29LV800D:
+		{
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + 0xAAA), 0xAA);
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + 0x555), 0x55);
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + 0x555), 0xA0);
+			break;
+		}
+		case FLASH_DEV_ID_AM29LV008B:
+		{
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + 0x555), 0xAA);
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + 0x2AA), 0x55);
+			asd_hwi_swb_write_byte(asd, 
+					(nv_flash_bar + 0x555), 0xA0);
+			break;
+		}
+		default:
+			break;
+		}
+		asd_hwi_swb_write_byte(asd, 
+				      (nv_flash_bar + nv_offset + i),
+				       0);
+		if (asd_hwi_poll_nvram(asd) != 0)
+			return (-1);
+	}
+#endif
+
+#if 0
+	/* Why is this required for program command sequence ?? */
+	/* Unlock bypass reset. */
+	asd_hwi_swb_write_byte(asd, nv_flash_bar, 0x90);
+	asd_hwi_swb_write_byte(asd, nv_flash_bar, 0x00);
+#endif
+	if (asd_hwi_reset_nvram(asd) != 0) {
+		return (-1);
+	}
+	return (0);
+}
+
+/*
+ * Function:
+ *	asd_hwi_erase_nv_sector()
+ *
+ * Description:
+ *	Erase the requested NVRAM sector.
+ */
+static int
+asd_hwi_erase_nv_sector(struct asd_softc *asd, uint32_t sector_addr) 
+{
+	uint32_t nv_flash_bar;
+	
+	nv_flash_bar = asd->hw_profile.nv_flash_bar; 
+	if (asd_hwi_poll_nvram(asd) != 0) {
+		return (-1);
+	}
+	/*
+	 * Erasing an NVRAM sector needs to be done in six consecutive
+	 * write cyles.
+	 */
+	switch (asd->hw_profile.flash_dev_id) {
+	case FLASH_DEV_ID_AM29LV800D:
+	{
+		asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0xAAA), 0xAA);
+		asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0x555), 0x55);
+		asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0xAAA), 0x80);
+		asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0xAAA), 0xAA);
+		asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0x555), 0x55);
+		asd_hwi_swb_write_byte(asd, (nv_flash_bar + sector_addr), 0x30);
+		break;
+	}
+	case FLASH_DEV_ID_AM29LV008B:
+	{
+		asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0x555), 0xAA);
+		asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0x2AA), 0x55);
+		asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0x555), 0x80);
+		asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0x555), 0xAA);
+		asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0x2AA), 0x55);
+		asd_hwi_swb_write_byte(asd, (nv_flash_bar + sector_addr), 0x30);
+		break;
+	}
+	default:
+		break;
+	}
+
+	if (asd_hwi_chk_write_status(asd, sector_addr, 
+				1 /* ERASE operation */) != 0) {
+		return (-1);
+	}
+	return (0);
+}
+
+/*
+ * Function:
+ * 	asd_hwi_verify_nv_checksum()
+ *	
+ * Description:
+ *	Verify if the checksum for particular NV segment is correct.
+ */		 	  
+static int
+asd_hwi_verify_nv_checksum(struct asd_softc *asd, u_int segment_id,
+			   uint8_t *segment_ptr, u_int bytes_to_read)
+{
+	uint32_t		offset;
+	u_int			pad_size;
+	u_int			image_size;
+	u_int			attr;
+	u_int			bytes_read;
+	int			checksum_bytes;
+	uint16_t		sum;
+	uint16_t		*seg_ptr;
+	u_short			i;
+	
+	if (asd_hwi_search_nv_segment(asd, segment_id, &offset, &pad_size,
+				      &image_size, &attr) != 0) {
+		asd_log(ASD_DBG_ERROR, "Requested segment not found in "
+			"the NVRAM.\n");
+		return (-1);
+	}
+
+	memset(segment_ptr, 0x0, bytes_to_read);
+	if (asd_hwi_read_nv_segment(asd, NVRAM_NO_SEGMENT_ID, segment_ptr,
+				    offset, bytes_to_read, &bytes_read) != 0) {
+		asd_log(ASD_DBG_ERROR, "Failed to read NVRAM segment %d.\n",
+			NVRAM_NO_SEGMENT_ID);
+		return (-1);
+	}
+
+	if (asd_hwi_reset_nvram(asd) != 0) {
+		asd_log(ASD_DBG_ERROR, "Failed to reset NVRAM.\n");
+		return (-1);
+	}
+
+	seg_ptr = (uint16_t *) segment_ptr;
+	/*
+	 * checksum_bytes is equivalent to the size of the layout.
+         * The size of layout is available at the offset of 8.
+	 */	 
+	checksum_bytes = (*(seg_ptr + 4) / 2);
+	offset += asd->hw_profile.nv_flash_bar;
+	sum = 0;
+	
+	for (i = 0; i < checksum_bytes; i++)
+		sum += asd_hwi_swb_read_word(asd, (offset + (i*2)));
+
+	if (sum != 0) {
+		asd_log(ASD_DBG_ERROR, "Checksum verification failed.\n");
+		return (-1);
+	}
+
+	return (0);
+}
+
+/*
+ * Function:
+ * 	asd_hwi_get_nv_config() 
+ *
+ * Description:
+ *	Retrieves NVRAM configuration for the controller.
+ */	   
+static int
+asd_hwi_get_nv_config(struct asd_softc  *asd)
+{
+	struct asd_manuf_base_seg_layout manuf_layout;
+	struct asd_pci_layout	pci_layout;
+	uint32_t		offset;
+	u_int			bytes_read;
+
+	if (asd_hwi_check_flash(asd) < 0) 
+		return -1;
+
+	asd->hw_profile.nv_exist = 0;
+	/* Verify if the controller NVRAM has CTRL_A_SETTING type. */
+	if (asd_hwi_verify_nv_checksum(asd, NVRAM_CTRL_A_SETTING,
+				       (uint8_t *) &pci_layout,
+				       sizeof(struct asd_pci_layout)) != 0) {
+		/* 
+		 * CTRL_A type verification failed, verify if the controller  
+		 * has NVRAM CTRL_A_DEFAULT type.
+		 */  
+		if (asd_hwi_verify_nv_checksum(asd, NVRAM_CTRL_A_DEFAULT,
+					       (uint8_t *) &pci_layout, 
+					       sizeof(struct asd_pci_layout))
+					       != 0)
+			return (-1);
+
+		asd->hw_profile.nv_segment_type = NVRAM_CTRL_A_DEFAULT;
+	} else {
+		asd->hw_profile.nv_segment_type = NVRAM_CTRL_A_SETTING;
+		asd->hw_profile.nv_exist = 1;
+	}
+
+	offset = 0;
+	memset(&manuf_layout, 0x0, sizeof (struct asd_manuf_base_seg_layout));
+
+	/* Retrieves Controller Manufacturing NVRAM setting. */
+	if (asd_hwi_read_nv_segment(asd, NVRAM_MANUF_TYPE, &manuf_layout,
+			    offset, sizeof(struct asd_manuf_base_seg_layout),
+			    &bytes_read) != 0) {
+		return (-1);
+	}
+
+	memcpy(asd->hw_profile.wwn, manuf_layout.base_sas_addr, 
+	       ASD_MAX_WWN_LEN);
+
+	return (0);
+}	
+
+/*
+ * Function:
+ * 	asd_hwi_search_nv_id()
+ *
+ * Description:
+ * 	Search for the requested NV setting ID. If successful copy contents
+ * 	to the destination buffer and set appropriate value in offset.
+ */
+static int
+asd_hwi_search_nv_id(struct asd_softc  *asd, u_int setting_id, void *dest,
+		     u_int  *src_offset, u_int bytes_to_read)
+{
+	struct asd_settings_layout *psettings;
+	uint32_t offset;
+	int bytes_read;
+
+	offset = 0;
+	bytes_read = 0;
+	psettings = (struct asd_settings_layout *)dest;
+	while (1) {
+		
+        	if (asd_hwi_read_nv_segment(asd, 
+				    asd->hw_profile.nv_segment_type, 
+				    psettings,
+				    offset, 
+				    bytes_to_read,
+				    &bytes_read) != 0) {
+			return (-1);
+		}
+
+		if (psettings->id == setting_id) {
+            		break;
+        	} else {
+			
+            		if (psettings->next_ptr == 0)
+				return -1;
+			offset = (uint32_t)offset + psettings->next_ptr;
+		}
+	}
+	*src_offset = offset;
+	return (0);
+}
+
+static int
+asd_hwi_get_nv_phy_settings(struct asd_softc *asd)
+{
+	struct asd_phy_settings_layout phy_settings;
+	struct asd_phy_settings_entry_layout *phy_entry;
+	uint32_t		offset;
+	uint32_t		bytes_to_read;
+	struct asd_phy		*phy;
+	u_int			bytes_read;
+	u_int		 	phy_id;
+	u_char		 	settings_found;
+
+	if (asd->hw_profile.nv_exist != 1)
+		return -1;
+	/*
+	 * NVRAM setting exists, retrieve Phys user settings, such 
+	 * as SAS address, connection rate, and attributes.
+	 */  
+	
+	offset = 0;
+	settings_found = 0;
+	if (asd_hwi_search_nv_id(asd, NVRAM_PHY_SETTINGS_ID, 
+			&phy_settings, &offset, sizeof(phy_settings))) {
+		return -1;
+	}
+
+	bytes_to_read = phy_settings.num_phys * sizeof(*phy_entry);
+	if ((phy_entry = asd_alloc_mem(bytes_to_read, GFP_ATOMIC)) 
+	   == NULL) {
+		return (-1);
+	}
+
+	if (asd_hwi_read_nv_segment(asd, 
+		    asd->hw_profile.nv_segment_type, 
+		    phy_entry,
+		    offset + sizeof(phy_settings),
+		    bytes_to_read,
+		    &bytes_read) != 0) {
+		asd_free_mem(phy_entry);
+		return (-1);
+	} else {
+		settings_found = 1;
+	}
+
+	if (settings_found == 1) {
+		asd->hw_profile.max_phys = phy_settings.num_phys;
+		for (phy_id = 0; 
+		     phy_id < asd->hw_profile.max_phys; 
+		     phy_id++) {
+			
+			if ((phy = asd->phy_list[phy_id]) == NULL)
+				continue;
+			
+			memcpy(phy->sas_addr, 
+			       phy_entry[phy_id].sas_addr,
+			       SAS_ADDR_LEN);
+			/* 
+			 * if an invalid link rate is specified,
+			 * the existing default value is used. 
+			 */
+			asd_hwi_map_lrate_from_sas(
+				(phy_entry[phy_id].link_rate 
+				 & PHY_MIN_LINK_RATE_MASK),
+				&phy->min_link_rate);
+
+			asd_hwi_map_lrate_from_sas(
+				((phy_entry[phy_id].link_rate
+				 & PHY_MAX_LINK_RATE_MASK) >> 4),
+				&phy->max_link_rate);
+			/* TBD: crc */
+		}
+	}
+	
+	asd_free_mem(phy_entry);
+	return 0;
+}
+
+static void
+asd_hwi_get_nv_phy_params(struct asd_softc *asd)
+{
+	struct asd_manuf_base_seg_layout   manuf_layout;	
+	struct asd_manuf_phy_param_layout  phy_param;
+	struct asd_phy_desc_format	  *pphy_desc;
+	struct asd_phy			  *phy;
+	uint32_t 	 		   offset;
+	uint32_t	 		   bytes_to_read;
+	uint32_t 	 		   bytes_read;
+	u_int 		 		   phy_id;
+	
+	/* Set the phy parms to the default value. */
+	for (phy_id = 0; phy_id < asd->hw_profile.max_phys; phy_id++) {
+		if ((phy = asd->phy_list[phy_id]) == NULL)
+			continue;
+
+		phy->phy_state = PHY_STATE_DEFAULT;
+		phy->phy_ctl0 = PHY_CTL0_DEFAULT;
+		phy->phy_ctl1 = PHY_CTL1_DEFAULT;
+		phy->phy_ctl2 = PHY_CTL2_DEFAULT;
+		phy->phy_ctl3 = PHY_CTL3_DEFAULT;
+	}
+
+	if (asd_hwi_verify_nv_checksum(asd, NVRAM_MANUF_TYPE,
+				       (uint8_t *) &manuf_layout,
+				       sizeof(struct asd_manuf_base_seg_layout))
+				       != 0) {
+		asd_log(ASD_DBG_ERROR, "Failed verifying checksum for "
+			"NVRAM MANUFACTURING LAYOUT.\n");
+		goto exit;
+	}
+
+	offset = 0;
+	memset(&phy_param, 0x0, sizeof(phy_param));
+
+	if (asd_hwi_get_nv_manuf_seg(asd, &phy_param,
+				     sizeof(phy_param), &offset,
+				     NVRAM_MNF_PHY_PARAM_SIGN) != 0)
+		goto exit;
+
+	if (phy_param.num_phy_desc > asd->hw_profile.max_phys)
+		phy_param.num_phy_desc = asd->hw_profile.max_phys;
+	
+	bytes_to_read = phy_param.num_phy_desc * phy_param.phy_desc_size;
+	if ((pphy_desc = asd_alloc_mem(bytes_to_read, GFP_ATOMIC)) == NULL)
+		goto exit;
+
+	offset += sizeof(phy_param);
+	if (asd_hwi_read_nv_segment(asd, NVRAM_MANUF_TYPE, pphy_desc, offset,
+				    bytes_to_read, &bytes_read) != 0) {
+		asd_free_mem(pphy_desc);
+		goto exit;
+	}
+
+	for (phy_id = 0; phy_id < asd->hw_profile.max_phys; phy_id++) {
+		if ((phy = asd->phy_list[phy_id]) == NULL)
+			continue;
+
+		phy->phy_state = (pphy_desc[phy_id].state & PHY_STATE_MASK);
+		phy->phy_ctl0 &= ~PHY_CTL0_MASK;
+		phy->phy_ctl0 |= (pphy_desc[phy_id].ctl0 & PHY_CTL0_MASK);
+		phy->phy_ctl1 = pphy_desc[phy_id].ctl1;
+		phy->phy_ctl2 = pphy_desc[phy_id].ctl2;
+		phy->phy_ctl3 = pphy_desc[phy_id].ctl3;
+	}
+
+	asd_free_mem(pphy_desc);
+exit:	
+	return;
+}
+
+/*
+ * Function:
+ *	asd_hwi_get_nv_conn_info
+ *
+ * Description:
+ * 	This routine reads the Connector Map information from Flash (NVRAM)
+ * 	and allocates memory and populates the parameters 'pconn' 
+ * 	and 'pnoded' with the read information. 
+ *
+ * 	At the end of this routine if everything goes fine the data structure 
+ * 	layout pointed to by 'pconn' and 'pnoded' will be:
+ *
+ * 	'pconn' ->
+ * 	|-----------|--------------|--------------|--------
+ * 	| Connector | Connector    | Connector    | ...
+ * 	| Map 	    | Descriptor 0 | Descriptor 1 | 
+ * 	|-----------|--------------|--------------|--------
+ * 	
+ * 	'pnoded' ->
+ * 	|--------|--------|--------|---  --|-------|--------|--------|----
+ * 	| Node   | Phy    | Phy    |...    | Node  | Phy    | Phy    | ...
+ * 	| Desc 0 | Desc 0 | Desc 1 |       | Desc 1| Desc 0 | Desc 1 |
+ * 	|--------|--------|--------|---  --|-------|--------|--------|----
+ *
+ * 	'pconn_size' will hold the size of data pointed to by 'pconn' 
+ * 	'pnoded_size' will hold the size of data pointed to by 'pnoded'. 
+ * 	
+ */
+int
+asd_hwi_get_nv_conn_info(struct asd_softc *asd,
+			 uint8_t **pconn,
+			 uint32_t *pconn_size,
+			 uint8_t **pnoded,
+			 uint32_t *pnoded_size)
+{
+	struct asd_manuf_base_seg_layout   manuf_layout;	
+	struct asd_conn_map_format conn_map;
+	struct asd_node_desc_format node_desc;
+	//struct asd_conn_desc_format *pconn_desc;
+	uint8_t *pconn_buf; 
+	uint8_t *pnode_buf; 
+	uint32_t offset, cur_offset;
+	uint32_t bytes_to_read, bytes_read;
+	uint32_t bytes_count, tot_count;
+	uint32_t node_num;
+	
+	if (pconn == NULL || pnoded == NULL || pconn_size == NULL 
+	   || pnoded_size == NULL) {
+		return -1;
+	}
+
+	if (asd_hwi_verify_nv_checksum(asd, NVRAM_MANUF_TYPE,
+				       (uint8_t *) &manuf_layout,
+				       sizeof(manuf_layout))
+				       != 0) {
+		asd_log(ASD_DBG_ERROR, "Failed verifying checksum for "
+			"NVRAM MANUFACTURING LAYOUT.\n");
+		return -1;
+	}
+
+	/* 
+	 * Determine the memory size to be allocated by reading
+	 * the connector_map first
+	 */
+	offset = 0;
+	memset(&conn_map, 0x0, sizeof(conn_map));
+	if (asd_hwi_get_nv_manuf_seg(asd, &conn_map, 
+				sizeof(conn_map), &offset,
+				NVRAM_MNF_CONN_MAP) != 0){
+		return (-1);
+	}
+	
+	bytes_to_read = sizeof(conn_map) 
+			+ conn_map.num_conn_desc * conn_map.conn_desc_size;
+	
+	/* 
+	 * Now read connector_map and associated connector_decriptor entries
+	 */
+	*pconn_size = bytes_to_read;
+	if ((pconn_buf = asd_alloc_mem(bytes_to_read, GFP_ATOMIC)) 
+	   == NULL) {
+		return (-1);
+	}
+	memset(pconn_buf, 0, bytes_to_read);
+	if (asd_hwi_read_nv_segment(asd, NVRAM_MANUF_TYPE,
+				pconn_buf, offset,
+				bytes_to_read,
+				&bytes_read) != 0) {
+		asd_free_mem(pconn_buf);
+		return (-1);
+	}
+
+	/* bypass all read connector_map & connector_descriptor entries */
+	offset += (bytes_read + 1);
+	
+	/* no field in connector map yet */
+	bytes_to_read = sizeof(node_desc); 
+
+	/* 
+	 * Determine memory to be allocated for reading node descriptors
+	 * and their associated phy descriptor entries
+	 */
+	bytes_count = 0;
+	tot_count = 0;
+	cur_offset = offset;
+	for (node_num = 0; node_num < conn_map.num_node_desc; node_num++) {
+		if (asd_hwi_read_nv_segment(asd, NVRAM_MANUF_TYPE,
+					&node_desc, cur_offset,
+					bytes_to_read,
+					&bytes_read) != 0) {
+			asd_free_mem(pconn_buf);
+			return (-1);
+		}
+		bytes_count = (sizeof(node_desc) 
+				+ (node_desc.num_phy_desc 
+				   * node_desc.phy_desc_size));
+		cur_offset += bytes_count;
+		tot_count += bytes_count;
+	}
+	bytes_to_read = tot_count;
+	
+	/* 
+	 * Now read node descriptors and their 
+	 * associated phy descriptor entries
+	 */
+	if ((pnode_buf = asd_alloc_mem(bytes_to_read, GFP_ATOMIC)) 
+	   == NULL) {
+		asd_free_mem(pconn_buf);
+		return (-1);
+	}
+	if (asd_hwi_read_nv_segment(asd, NVRAM_MANUF_TYPE,
+				pnode_buf, offset,
+				bytes_to_read,
+				&bytes_read) != 0) {
+		asd_free_mem(pconn_buf);
+		asd_free_mem(pnode_buf);
+		return (-1);
+	}
+
+	/* Caller has to free the resources */
+	*pconn = pconn_buf;
+	*pnoded = pnode_buf;
+	*pnoded_size = tot_count;
+
+	return 0;
+}
+
+static int
+asd_hwi_get_nv_manuf_seg(struct asd_softc *asd, void *dest, 
+			 uint32_t bytes_to_read, uint32_t *src_offset,
+			 uint16_t signature) 
+{
+	struct asd_manuf_base_seg_layout manuf_layout;
+	uint32_t 			 offset;
+	uint32_t 			 bytes_read;
+	int				 segments_size;
+	int				 segment_off;
+	int 				 err;
+	
+	memset(&manuf_layout, 0x0, sizeof(manuf_layout));
+	err = -1;
+	offset = 0;
+
+	/* Retrieve Manufacturing Base segment */
+	if (asd_hwi_read_nv_segment(asd, NVRAM_MANUF_TYPE, &manuf_layout,
+				    offset, sizeof(manuf_layout), 
+				    &bytes_read) != 0) {
+		return err;
+	}
+
+	/* Retrieve Manufacturing segment specified by signature */
+	offset = manuf_layout.seg_sign.next_seg_ptr;
+	segments_size = manuf_layout.sector_size - sizeof(manuf_layout); 
+	segment_off = offset;
+
+	while (1) {
+		if (asd_hwi_read_nv_segment(asd, NVRAM_MANUF_TYPE,
+					    dest, offset, bytes_to_read,
+					    &bytes_read) != 0) {
+			return (-1);
+		}
+
+		if (((struct asd_manuf_seg_sign *) dest)->signature ==
+			signature) {
+			*src_offset = offset;
+			err = 0;
+			break;
+		}
+		
+		/*
+		 * If next_seg_ptr is 0 then it indicates the last segment.
+		 */
+		if (((struct asd_manuf_seg_sign *) dest)->next_seg_ptr == 0)
+			break;
+
+		offset = ((struct asd_manuf_seg_sign *) dest)->next_seg_ptr;
+	}
+
+	return err;
+}
+
+static int
+asd_hwi_map_lrate_from_sas(u_int sas_link_rate, 
+		u_int *asd_link_rate)
+{
+	switch (sas_link_rate) {
+	case SAS_RATE_30GBPS:
+		*asd_link_rate = SAS_30GBPS_RATE; 
+		break;
+	case SAS_RATE_15GBPS:
+		*asd_link_rate = SAS_15GBPS_RATE;
+		break;
+	default:
+		return -1;
+	}
+	return 0;
+}
+
+static int
+asd_hwi_set_speed_mask(u_int asd_link_rate, 
+				  uint8_t *asd_speed_mask)
+{
+	switch (asd_link_rate) {
+	case SAS_60GBPS_RATE:
+		*asd_speed_mask &= ~SAS_SPEED_60_DIS; 
+		break;
+	case SAS_15GBPS_RATE:
+		*asd_speed_mask &= ~SAS_SPEED_15_DIS; 
+		break;
+	case SAS_30GBPS_RATE:
+	default:
+		*asd_speed_mask &= ~SAS_SPEED_30_DIS; 
+		break;
+	}
+
+	return 0;
+}
+
+static int
+asd_hwi_get_ocm_info(struct asd_softc  *asd)
+{
+	struct asd_ocm_entry_format ocm_de;
+	struct asd_bios_chim_format *pbios_chim;
+	uint32_t offset, bytes_read, bytes_to_read;
+
+	if (asd_hwi_get_ocm_entry(asd, OCM_DE_BIOS_CHIM, 
+				  &ocm_de, &offset) != 0) {
+		return -1;
+	}
+
+	bytes_to_read = OCM_DE_OFFSET_SIZE_CONV(ocm_de.size);
+	if ((pbios_chim = asd_alloc_mem(bytes_to_read, GFP_ATOMIC)) 
+	   == NULL) {
+		return (-1);
+	}
+	if (asd_hwi_read_ocm_seg(asd, pbios_chim, 
+		OCM_DE_OFFSET_SIZE_CONV(ocm_de.offset), 
+		bytes_to_read, 
+		&bytes_read) != 0) {
+		asd_free_mem(pbios_chim);
+		return -1;
+	}
+	if (pbios_chim->signature == OCM_BC_BIOS_SIGN) {
+		if (pbios_chim->bios_present 
+		   & OCM_BC_BIOS_PRSNT_MASK) {
+			asd->hw_profile.bios_maj_ver = 
+					pbios_chim->bios_maj_ver;
+			asd->hw_profile.bios_min_ver = 
+					pbios_chim->bios_min_ver;
+			asd->hw_profile.bios_bld_num = 
+					pbios_chim->bios_bld_num;
+		}
+	}
+
+	asd->unit_elements = asd_alloc_mem(pbios_chim->num_elements * 
+		sizeof(struct asd_unit_element_format), GFP_ATOMIC);
+
+	if (asd->unit_elements == NULL) {
+		asd_free_mem(pbios_chim);
+		return 0;
+	}
+
+	asd->num_unit_elements = pbios_chim->num_elements;
+
+	memcpy(asd->unit_elements, &pbios_chim->unit_elm[0],
+		pbios_chim->num_elements * 
+			sizeof(struct asd_unit_element_format));
+
+	asd_free_mem(pbios_chim);
+
+	return 0;
+}
+
+static int
+asd_hwi_get_ocm_entry(struct asd_softc  *asd, 
+				 uint32_t entry_type, 
+				 struct asd_ocm_entry_format *pocm_de, 
+				 uint32_t *src_offset)
+{
+	struct asd_ocm_dir_format ocm_dir;
+	uint32_t offset, entry_no;
+	u_int bytes_read;
+	int err;
+
+	memset(&ocm_dir, 0x0, sizeof (ocm_dir));
+	memset(pocm_de, 0x0, sizeof (*pocm_de));
+	offset = 0;
+	err = -1;
+	
+	if (asd_hwi_read_ocm_seg(asd, &ocm_dir, offset, 
+		sizeof(ocm_dir), &bytes_read)) {
+		return -1;
+	}
+
+	if ((ocm_dir.signature != OCM_DIR_SIGN) 
+	   /*|| ((ocm_dir.num_entries & OCM_NUM_ENTRIES_MASK) == 0)*/) {
+		return -1;
+	}
+
+	offset = sizeof(ocm_dir);
+	for (entry_no = 0; 
+	     entry_no < (ocm_dir.num_entries & OCM_NUM_ENTRIES_MASK); 
+	     entry_no++, offset += sizeof(*pocm_de)) {
+		if (asd_hwi_read_ocm_seg(asd, pocm_de, offset, 
+			sizeof(*pocm_de), &bytes_read)) {
+			return -1;
+		}
+		if (pocm_de->type == entry_type) {
+			err = 0;
+			break;
+		}
+	}
+	*src_offset = offset;
+	return err;
+}
+
+static int
+asd_hwi_read_ocm_seg(struct asd_softc *asd, void *dest,
+			uint32_t src_offset, u_int bytes_to_read,
+			u_int *bytes_read)
+{
+    	uint8_t		*dest_buf;
+    	uint32_t	 nv_offset;
+	uint32_t	 i;
+	
+	nv_offset = 0;
+	nv_offset = OCM_BASE_ADDR + nv_offset + src_offset;
+	
+	dest_buf = (uint8_t *) dest;
+	*bytes_read = 0;
+	
+	for (i = 0; i < bytes_to_read; i++) {
+		*(dest_buf+i) = asd_hwi_swb_read_byte(asd, (nv_offset + i));
+	}
+	*bytes_read = i - 1;
+
+	return (0); 
+}
+
+static int asd_hwi_check_flash(struct asd_softc *asd) 
+{
+	uint32_t nv_flash_bar;
+	uint32_t exsi_base_addr;
+	uint32_t reg_contents;
+	uint8_t manuf_id;
+	uint8_t dev_id_boot_blk;
+	uint8_t sec_prot;
+
+	/* get Flash memory base address */
+	asd->hw_profile.nv_flash_bar = 
+				asd_pcic_read_dword(asd, PCIC_FLASH_MBAR);
+	nv_flash_bar = asd->hw_profile.nv_flash_bar; 
+
+	/* check presence of flash */	
+	exsi_base_addr = EXSI_REG_BASE_ADR + EXSICNFGR;
+	reg_contents = asd_hwi_swb_read_dword(asd, exsi_base_addr);
+	if (!(reg_contents & FLASHEX)) {
+		asd->hw_profile.flash_present = FALSE;
+		return -1;
+	}
+	asd->hw_profile.flash_present = TRUE;
+	
+	/* Determine flash info */
+	if (asd_hwi_reset_nvram(asd) != 0)
+		return (-1);
+
+	asd->hw_profile.flash_manuf_id = FLASH_MANUF_ID_UNKNOWN;
+	asd->hw_profile.flash_dev_id = FLASH_DEV_ID_UNKNOWN;
+
+	/* Issue Unlock sequence for AM29LV800D */
+ 	asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0xAAA), 0xAA);
+ 	asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0x555), 0x55);
+
+	/* Issue the erase command */
+	asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0xAAA), 0x90);
+
+	manuf_id = asd_hwi_swb_read_byte(asd, nv_flash_bar);
+	dev_id_boot_blk = asd_hwi_swb_read_byte(asd, nv_flash_bar + 1);
+	sec_prot = asd_hwi_swb_read_byte(asd, nv_flash_bar + 2);
+
+	if (asd_hwi_reset_nvram(asd) != 0)
+		return (-1);
+
+	if ((manuf_id == (uint8_t)FLASH_MANUF_ID_AMD 
+	   && sec_prot == (uint8_t)FLASH_DEV_ID_AM29LV800D)){
+		/* TBD: check the data order */
+		asd->hw_profile.flash_manuf_id = manuf_id;
+		asd->hw_profile.flash_dev_id = sec_prot;
+		//asd->hw_profile.flash_wr_prot = ;
+		return 0; 
+	}
+	
+	if (asd_hwi_reset_nvram(asd) != 0)
+		return (-1);
+
+	/* Issue Unlock sequence for AM29LV008BT */
+	asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0x555), 0xAA);
+	asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0x2AA), 0x55);
+
+	asd_hwi_swb_write_byte(asd, (nv_flash_bar + 0x555), 0x90);
+
+	manuf_id = asd_hwi_swb_read_byte(asd, nv_flash_bar);
+	dev_id_boot_blk = asd_hwi_swb_read_byte(asd, nv_flash_bar + 1);
+	sec_prot = asd_hwi_swb_read_byte(asd, nv_flash_bar + 2);
+	
+	if (asd_hwi_reset_nvram(asd) != 0)
+		return (-1);
+	
+	if ((manuf_id == (uint8_t)FLASH_MANUF_ID_AMD 
+	    && dev_id_boot_blk == (uint8_t)FLASH_DEV_ID_AM29LV008B)){
+		asd->hw_profile.flash_manuf_id = manuf_id;
+		asd->hw_profile.flash_dev_id = dev_id_boot_blk;
+		asd->hw_profile.flash_wr_prot = sec_prot;
+		return 0;
+	}
+
+ 	return -1;
+}
+#endif /* NVRAM_SUPPORT */
+
+int
+asd_hwi_control_activity_leds(struct asd_softc *asd, uint8_t phy_id, 
+			      uint32_t asd_phy_ctl_func)
+{
+	uint32_t	exsi_base_addr;
+	uint32_t	reg_contents;
+	uint32_t	reg_data;
+	
+	if (asd->hw_profile.rev_id != AIC9410_DEV_REV_B0) 
+		return -EINVAL;
+
+	if (phy_id >= ASD_MAX_XDEVLED_BITS)
+		return -EINVAL;
+
+	reg_data = 0;
+
+	/* set the bit map corresponding to phy_id */
+	reg_data = (1 << phy_id);
+
+	/* Enable/Disable activity LED for the PHY */
+	exsi_base_addr = EXSI_REG_BASE_ADR + GPIOOER;
+	reg_contents = asd_hwi_swb_read_dword(asd, exsi_base_addr);
+
+	if (asd_phy_ctl_func == DISABLE_PHY) {
+		reg_contents &= ~reg_data;
+	} else {
+		if (asd_phy_ctl_func == ENABLE_PHY
+		   || asd_phy_ctl_func == ENABLE_PHY_NO_SAS_OOB
+		   || asd_phy_ctl_func == ENABLE_PHY_NO_SATA_OOB ) {
+			reg_contents |= reg_data;
+		}
+	}
+	asd_hwi_swb_write_dword(asd, exsi_base_addr, reg_contents);
+	
+	/* Set activity source to external/internal */
+	exsi_base_addr = EXSI_REG_BASE_ADR + GPIOCNFGR;
+	reg_contents = asd_hwi_swb_read_dword(asd, exsi_base_addr);
+
+	if (asd_phy_ctl_func == DISABLE_PHY) {
+		reg_contents &= ~reg_data;
+	} else {
+		if (asd_phy_ctl_func == ENABLE_PHY
+		   || asd_phy_ctl_func == ENABLE_PHY_NO_SAS_OOB
+		   || asd_phy_ctl_func == ENABLE_PHY_NO_SATA_OOB ) {
+			reg_contents |= reg_data;
+		}
+	}
+	asd_hwi_swb_write_dword(asd, exsi_base_addr, reg_contents);
+
+	return 0;
+}
+




                 reply	other threads:[~2005-02-17 17:36 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4214D613.2000006@adaptec.com \
    --to=luben_tuikov@adaptec.com \
    --cc=linux-scsi@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.