* [ANNOUNCE] Adaptec SAS/SATA device driver [09/27]
@ 2005-02-17 17:36 Luben Tuikov
0 siblings, 0 replies; only message in thread
From: Luben Tuikov @ 2005-02-17 17:36 UTC (permalink / raw)
To: SCSI Mailing List
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;
+}
+
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2005-02-17 17:36 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-02-17 17:36 [ANNOUNCE] Adaptec SAS/SATA device driver [09/27] Luben Tuikov
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.