/* $NetBSD: adwlib.c,v 1.2 1998/09/26 19:54:22 dante Exp $ */ /* * Low level routines for the Advanced Systems Inc. SCSI controllers chips * * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * Author: Baldassare Dante Profeta * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the NetBSD * Foundation, Inc. and its contributors. * 4. Neither the name of The NetBSD Foundation nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Ported from: */ /* * advansys.c - Linux Host Driver for AdvanSys SCSI Adapters * * Copyright (c) 1995-1998 Advanced System Products, Inc. * All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that redistributions of source * code retain the above copyright notice and this comment without * modification. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Static Functions */ static u_int16_t AdvGetEEPConfig __P((bus_space_tag_t, bus_space_handle_t, ADWEEP_CONFIG *)); static u_int16_t AdvReadEEPWord __P((bus_space_tag_t, bus_space_handle_t, int)); static void AdvWaitEEPCmd __P((bus_space_tag_t, bus_space_handle_t)); static void AdvSetEEPConfig __P((bus_space_tag_t, bus_space_handle_t, ADWEEP_CONFIG *)); static int AdvSendScsiCmd __P((ADW_SOFTC *, ADW_SCSI_REQ_Q *)); static void AdvInquiryHandling __P((ADW_SOFTC *, ADW_SCSI_REQ_Q *)); static void DvcSleepMilliSecond __P((ulong)); static void DvcDelayMicroSecond __P((ulong)); /* * EEPROM Configuration. * * All drivers should use this structure to set the default EEPROM * configuration. The BIOS now uses this structure when it is built. * Additional structure information can be found in advlib.h where * the structure is defined. */ static ADWEEP_CONFIG Default_EEPROM_Config = { ADW_EEPROM_BIOS_ENABLE, /* cfg_msw */ 0x0000, /* cfg_lsw */ 0xFFFF, /* disc_enable */ 0xFFFF, /* wdtr_able */ 0xFFFF, /* sdtr_able */ 0xFFFF, /* start_motor */ 0xFFFF, /* tagqng_able */ 0xFFFF, /* bios_scan */ 0, /* scam_tolerant */ 7, /* adapter_scsi_id */ 0, /* bios_boot_delay */ 3, /* scsi_reset_delay */ 0, /* bios_id_lun */ 0, /* termination */ 0, /* reserved1 */ 0xFFEF, /* bios_ctrl */ 0xFFFF, /* ultra_able */ 0, /* reserved2 */ ASC_DEF_MAX_HOST_QNG, /* max_host_qng */ ASC_DEF_MAX_DVC_QNG, /* max_dvc_qng */ 0, /* dvc_cntl */ 0, /* bug_fix */ 0, /* serial_number_word1 */ 0, /* serial_number_word2 */ 0, /* serial_number_word3 */ 0, /* check_sum */ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, /* oem_name[16] */ 0, /* dvc_err_code */ 0, /* adv_err_code */ 0, /* adv_err_addr */ 0, /* saved_dvc_err_code */ 0, /* saved_adv_err_code */ 0, /* saved_adv_err_addr */ 0 /* num_of_err */ }; /* * Initialize the ASC3550. * * On failure set the ADW_SOFTC field 'err_code' and return ADW_ERROR. * * For a non-fatal error return a warning code. If there are no warnings * then 0 is returned. */ int AdvInitAsc3550Driver(sc) ADW_SOFTC *sc; { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int16_t warn_code; u_int32_t sum; int begin_addr; int end_addr; int code_sum; int word; int rql_addr; /* RISC Queue List address */ int i; u_int16_t scsi_cfg1; u_int8_t biosmem[ASC_MC_BIOSLEN]; /* BIOS RISC Memory * 0x40-0x8F */ warn_code = 0; /* * Save the RISC memory BIOS region before writing the microcode. * The BIOS may already be loaded and using its RISC LRAM region * so its region must be saved and restored. * * Note: This code makes the assumption, which is currently true, * that a chip reset does not clear RISC LRAM. */ for (i = 0; i < ASC_MC_BIOSLEN; i++) { ADW_READ_BYTE_LRAM(iot, ioh, ASC_MC_BIOSMEM + i, biosmem[i]); } /* * Load the Microcode * * Write the microcode image to RISC memory starting at address 0. */ ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_RAM_ADDR, 0); for (word = 0; word < adv_mcode_size; word += 2) { ADW_WRITE_WORD_AUTO_INC_LRAM(iot, ioh, *((u_int16_t *) (&adv_mcode[word]))); } /* * Clear the rest of Condor's Internal RAM (8KB). */ for (; word < ADW_CONDOR_MEMSIZE; word += 2) { ADW_WRITE_WORD_AUTO_INC_LRAM(iot, ioh, 0); } /* * Verify the microcode checksum. */ sum = 0; ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_RAM_ADDR, 0); for (word = 0; word < adv_mcode_size; word += 2) { sum += ADW_READ_WORD_AUTO_INC_LRAM(iot, ioh); } if (sum != adv_mcode_chksum) return ASC_IERR_MCODE_CHKSUM; /* * Restore the RISC memory BIOS region. */ for (i = 0; i < ASC_MC_BIOSLEN; i++) { ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_BIOSMEM + i, biosmem[i]); } /* * Calculate and write the microcode code checksum to the microcode * code checksum location ASC_MC_CODE_CHK_SUM (0x2C). */ ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_CODE_BEGIN_ADDR, begin_addr); ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_CODE_END_ADDR, end_addr); code_sum = 0; for (word = begin_addr; word < end_addr; word += 2) { code_sum += *((u_int16_t *) (&adv_mcode[word])); } ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_CODE_CHK_SUM, code_sum); /* * Read microcode version and date. */ ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_VERSION_DATE, sc->cfg.mcode_date); ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_VERSION_NUM, sc->cfg.mcode_version); /* * Initialize microcode operating variables */ ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_ADAPTER_SCSI_ID, sc->chip_scsi_id); /* * If the PCI Configuration Command Register "Parity Error Response * Control" Bit was clear (0), then set the microcode variable * 'control_flag' CONTROL_FLAG_IGNORE_PERR flag to tell the microcode * to ignore DMA parity errors. */ if (sc->cfg.control_flag & CONTROL_FLAG_IGNORE_PERR) { ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_CONTROL_FLAG, word); word |= CONTROL_FLAG_IGNORE_PERR; ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_CONTROL_FLAG, word); } /* * Set default microcode operating variables for WDTR, SDTR, and * command tag queuing based on the EEPROM configuration values. * * These ADW_DVC_VAR fields and the microcode variables will be * changed in AdvInquiryHandling() if it is found a device is * incapable of a particular feature. */ /* * Set the microcode ULTRA target mask from EEPROM value. The * SDTR target mask overrides the ULTRA target mask in the * microcode so it is safe to set this value without determining * whether the device supports SDTR. * * Note: There is no way to know whether a device supports ULTRA * speed without attempting a SDTR ULTRA speed negotiation with * the device. The device will reject the speed if it does not * support it by responding with an SDTR message containing a * slower speed. */ ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_ULTRA_ABLE, sc->ultra_able); ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_DISC_ENABLE, sc->cfg.disc_enable); /* * Set SCSI_CFG0 Microcode Default Value. * * The microcode will set the SCSI_CFG0 register using this value * after it is started below. */ ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_DEFAULT_SCSI_CFG0, ADW_PARITY_EN | ADW_SEL_TMO_LONG | ADW_OUR_ID_EN | sc->chip_scsi_id); /* * Determine SCSI_CFG1 Microcode Default Value. * * The microcode will set the SCSI_CFG1 register using this value * after it is started below. */ /* Read current SCSI_CFG1 Register value. */ scsi_cfg1 = ADW_READ_WORD_REGISTER(iot, ioh, IOPW_SCSI_CFG1); /* * If all three connectors are in use, return an error. */ if ((scsi_cfg1 & CABLE_ILLEGAL_A) == 0 || (scsi_cfg1 & CABLE_ILLEGAL_B) == 0) { return ASC_IERR_ILLEGAL_CONNECTION; } /* * If the internal narrow cable is reversed all of the SCSI_CTRL * register signals will be set. Check for and return an error if * this condition is found. */ if ((ADW_READ_WORD_REGISTER(iot, ioh, IOPW_SCSI_CTRL) & 0x3F07) == 0x3F07) { return ASC_IERR_REVERSED_CABLE; } /* * If this is a differential board and a single-ended device * is attached to one of the connectors, return an error. */ if ((scsi_cfg1 & ADW_DIFF_MODE) && (scsi_cfg1 & ADW_DIFF_SENSE) == 0) return ASC_IERR_SINGLE_END_DEVICE; /* * If automatic termination control is enabled, then set the * termination value based on a table listed in advlib.h. * * If manual termination was specified with an EEPROM setting * then 'termination' was set-up in AdvInitFromEEP() and * is ready to be 'ored' into SCSI_CFG1. */ if (sc->cfg.termination == 0) { /* * The software always controls termination by setting * ADW_TERM_CTL_SEL. * If ADW_TERM_CTL_SEL were set to 0, the hardware would * set termination. */ sc->cfg.termination |= ADW_TERM_CTL_SEL; switch (scsi_cfg1 & ADW_CABLE_DETECT) { /* ADW_TERM_CTL_H: on, ADW_TERM_CTL_L: on */ case 0x3: case 0x7: case 0xB: case 0xD: case 0xE: case 0xF: sc->cfg.termination |= (ADW_TERM_CTL_H | ADW_TERM_CTL_L); break; /* ADW_TERM_CTL_H: on, ADW_TERM_CTL_L: off */ case 0x1: case 0x5: case 0x9: case 0xA: case 0xC: sc->cfg.termination |= ADW_TERM_CTL_H; break; /* ADW_TERM_CTL_H: off, ADW_TERM_CTL_L: off */ case 0x2: case 0x6: break; } } /* * Clear any set ADW_TERM_CTL_H and ADW_TERM_CTL_L bits. */ scsi_cfg1 &= ~ADW_TERM_CTL; /* * Invert the ADW_TERM_CTL_H and ADW_TERM_CTL_L bits and then * set 'scsi_cfg1'. The ADW_TERM_POL bit does not need to be * referenced, because the hardware internally inverts * the Termination High and Low bits if ADW_TERM_POL is set. */ scsi_cfg1 |= (ADW_TERM_CTL_SEL | (~sc->cfg.termination & ADW_TERM_CTL)); /* * Set SCSI_CFG1 Microcode Default Value * * Set filter value and possibly modified termination control * bits in the Microcode SCSI_CFG1 Register Value. * * The microcode will set the SCSI_CFG1 register using this value * after it is started below. */ ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_DEFAULT_SCSI_CFG1, ADW_FLTR_11_TO_20NS | scsi_cfg1); /* * Set SEL_MASK Microcode Default Value * * The microcode will set the SEL_MASK register using this value * after it is started below. */ ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_DEFAULT_SEL_MASK, ADW_TID_TO_TIDMASK(sc->chip_scsi_id)); /* * Link all the RISC Queue Lists together in a doubly-linked * NULL terminated list. * * Skip the NULL (0) queue which is not used. */ for (i = 1, rql_addr = ASC_MC_RISC_Q_LIST_BASE+ASC_MC_RISC_Q_LIST_SIZE; i < ASC_MC_RISC_Q_TOTAL_CNT; i++, rql_addr += ASC_MC_RISC_Q_LIST_SIZE) { /* * Set the current RISC Queue List's RQL_FWD and * RQL_BWD pointers in a one word write and set * the state (RQL_STATE) to free. */ ADW_WRITE_WORD_LRAM(iot, ioh, rql_addr, ((i + 1) + ((i - 1) << 8))); ADW_WRITE_BYTE_LRAM(iot, ioh, rql_addr + RQL_STATE, ASC_MC_QS_FREE); } /* * Set the Host and RISC Queue List pointers. * * Both sets of pointers are initialized with the same values: * ASC_MC_RISC_Q_FIRST(0x01) and ASC_MC_RISC_Q_LAST (0xFF). */ ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_HOST_NEXT_READY, ASC_MC_RISC_Q_FIRST); ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_HOST_NEXT_DONE, ASC_MC_RISC_Q_LAST); ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_RISC_NEXT_READY, ASC_MC_RISC_Q_FIRST); ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_RISC_NEXT_DONE, ASC_MC_RISC_Q_LAST); /* * Finally, set up the last RISC Queue List (255) with * a NULL forward pointer. */ ADW_WRITE_WORD_LRAM(iot, ioh, rql_addr, (ASC_MC_NULL_Q + ((i - 1) << 8))); ADW_WRITE_BYTE_LRAM(iot, ioh, rql_addr + RQL_STATE, ASC_MC_QS_FREE); ADW_WRITE_BYTE_REGISTER(iot, ioh, IOPB_INTR_ENABLES, (ADW_INTR_ENABLE_HOST_INTR | ADW_INTR_ENABLE_GLOBAL_INTR)); ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_CODE_BEGIN_ADDR, word); ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_PC, word); /* finally, finally, gentlemen, start your engine */ ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_RISC_CSR, ADW_RISC_CSR_RUN); return warn_code; } /* * Read the board's EEPROM configuration. Set fields in ADW_SOFTC and * ADW_DVC_CFG based on the EEPROM settings. The chip is stopped while * all of this is done. * * On failure set the ADW_DVC_VAR field 'err_code' and return ADW_ERROR. * * For a non-fatal error return a warning code. If there are no warnings * then 0 is returned. * * Note: Chip is stopped on entry. */ int AdvInitFromEEP(sc) ADW_SOFTC *sc; { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int16_t warn_code; ADWEEP_CONFIG eep_config; int eep_chksum, i; warn_code = 0; /* * Read the board's EEPROM configuration. * * Set default values if a bad checksum is found. */ eep_chksum = AdvGetEEPConfig(iot, ioh, &eep_config); if (eep_chksum != eep_config.check_sum) { warn_code |= ASC_WARN_EEPROM_CHKSUM; /* * Set EEPROM default values. */ for (i = 0; i < sizeof(ADWEEP_CONFIG); i++) { *((u_int8_t *) & eep_config + i) = *((u_int8_t *) & Default_EEPROM_Config + i); } /* * Assume the 6 byte board serial number that was read * from EEPROM is correct even if the EEPROM checksum * failed. */ eep_config.serial_number_word3 = AdvReadEEPWord(iot, ioh, ASC_EEP_DVC_CFG_END - 1); eep_config.serial_number_word2 = AdvReadEEPWord(iot, ioh, ASC_EEP_DVC_CFG_END - 2); eep_config.serial_number_word1 = AdvReadEEPWord(iot, ioh, ASC_EEP_DVC_CFG_END - 3); AdvSetEEPConfig(iot, ioh, &eep_config); } /* * Set ADW_DVC_VAR and ADW_DVC_CFG variables from the * EEPROM configuration that was read. * * This is the mapping of EEPROM fields to Adv Library fields. */ sc->wdtr_able = eep_config.wdtr_able; sc->sdtr_able = eep_config.sdtr_able; sc->ultra_able = eep_config.ultra_able; sc->tagqng_able = eep_config.tagqng_able; sc->cfg.disc_enable = eep_config.disc_enable; sc->max_host_qng = eep_config.max_host_qng; sc->max_dvc_qng = eep_config.max_dvc_qng; sc->chip_scsi_id = (eep_config.adapter_scsi_id & ADW_MAX_TID); sc->start_motor = eep_config.start_motor; sc->scsi_reset_wait = eep_config.scsi_reset_delay; sc->cfg.bios_boot_wait = eep_config.bios_boot_delay; sc->bios_ctrl = eep_config.bios_ctrl; sc->no_scam = eep_config.scam_tolerant; sc->cfg.serial1 = eep_config.serial_number_word1; sc->cfg.serial2 = eep_config.serial_number_word2; sc->cfg.serial3 = eep_config.serial_number_word3; /* * Set the host maximum queuing (max. 253, min. 16) and the per device * maximum queuing (max. 63, min. 4). */ if (eep_config.max_host_qng > ASC_DEF_MAX_HOST_QNG) { eep_config.max_host_qng = ASC_DEF_MAX_HOST_QNG; } else if (eep_config.max_host_qng < ASC_DEF_MIN_HOST_QNG) { /* If the value is zero, assume it is uninitialized. */ if (eep_config.max_host_qng == 0) { eep_config.max_host_qng = ASC_DEF_MAX_HOST_QNG; } else { eep_config.max_host_qng = ASC_DEF_MIN_HOST_QNG; } } if (eep_config.max_dvc_qng > ASC_DEF_MAX_DVC_QNG) { eep_config.max_dvc_qng = ASC_DEF_MAX_DVC_QNG; } else if (eep_config.max_dvc_qng < ASC_DEF_MIN_DVC_QNG) { /* If the value is zero, assume it is uninitialized. */ if (eep_config.max_dvc_qng == 0) { eep_config.max_dvc_qng = ASC_DEF_MAX_DVC_QNG; } else { eep_config.max_dvc_qng = ASC_DEF_MIN_DVC_QNG; } } /* * If 'max_dvc_qng' is greater than 'max_host_qng', then * set 'max_dvc_qng' to 'max_host_qng'. */ if (eep_config.max_dvc_qng > eep_config.max_host_qng) { eep_config.max_dvc_qng = eep_config.max_host_qng; } /* * Set ADW_DVC_VAR 'max_host_qng' and ADW_DVC_CFG 'max_dvc_qng' * values based on possibly adjusted EEPROM values. */ sc->max_host_qng = eep_config.max_host_qng; sc->max_dvc_qng = eep_config.max_dvc_qng; /* * If the EEPROM 'termination' field is set to automatic (0), then set * the ADW_DVC_CFG 'termination' field to automatic also. * * If the termination is specified with a non-zero 'termination' * value check that a legal value is set and set the ADW_DVC_CFG * 'termination' field appropriately. */ if (eep_config.termination == 0) { sc->cfg.termination = 0; /* auto termination */ } else { /* Enable manual control with low off / high off. */ if (eep_config.termination == 1) { sc->cfg.termination = ADW_TERM_CTL_SEL; /* Enable manual control with low off / high on. */ } else if (eep_config.termination == 2) { sc->cfg.termination = ADW_TERM_CTL_SEL | ADW_TERM_CTL_H; /* Enable manual control with low on / high on. */ } else if (eep_config.termination == 3) { sc->cfg.termination = ADW_TERM_CTL_SEL | ADW_TERM_CTL_H | ADW_TERM_CTL_L; } else { /* * The EEPROM 'termination' field contains a bad value. * Use automatic termination instead. */ sc->cfg.termination = 0; warn_code |= ASC_WARN_EEPROM_TERMINATION; } } return warn_code; } /* * Read EEPROM configuration into the specified buffer. * * Return a checksum based on the EEPROM configuration read. */ static u_int16_t AdvGetEEPConfig(iot, ioh, cfg_buf) bus_space_tag_t iot; bus_space_handle_t ioh; ADWEEP_CONFIG *cfg_buf; { u_int16_t wval, chksum; u_int16_t *wbuf; int eep_addr; wbuf = (u_int16_t *) cfg_buf; chksum = 0; for (eep_addr = ASC_EEP_DVC_CFG_BEGIN; eep_addr < ASC_EEP_DVC_CFG_END; eep_addr++, wbuf++) { wval = AdvReadEEPWord(iot, ioh, eep_addr); chksum += wval; *wbuf = wval; } *wbuf = AdvReadEEPWord(iot, ioh, eep_addr); wbuf++; for (eep_addr = ASC_EEP_DVC_CTL_BEGIN; eep_addr < ASC_EEP_MAX_WORD_ADDR; eep_addr++, wbuf++) { *wbuf = AdvReadEEPWord(iot, ioh, eep_addr); } return chksum; } /* * Read the EEPROM from specified location */ static u_int16_t AdvReadEEPWord(iot, ioh, eep_word_addr) bus_space_tag_t iot; bus_space_handle_t ioh; int eep_word_addr; { ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_CMD, ASC_EEP_CMD_READ | eep_word_addr); AdvWaitEEPCmd(iot, iot); return ADW_READ_WORD_REGISTER(iot, ioh, IOPW_EE_DATA); } /* * Wait for EEPROM command to complete */ static void AdvWaitEEPCmd(iot, ioh) bus_space_tag_t iot; bus_space_handle_t ioh; { DvcSleepMilliSecond(1); for (;;) { if (ADW_READ_WORD_REGISTER(iot, ioh, IOPW_EE_CMD) & ASC_EEP_CMD_DONE) { break; } DvcSleepMilliSecond(1); } return; } /* * Write the EEPROM from 'cfg_buf'. */ static void AdvSetEEPConfig(iot, ioh, cfg_buf) bus_space_tag_t iot; bus_space_handle_t ioh; ADWEEP_CONFIG *cfg_buf; { u_int16_t *wbuf; u_int16_t addr, chksum; wbuf = (u_int16_t *) cfg_buf; chksum = 0; ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_CMD, ASC_EEP_CMD_WRITE_ABLE); AdvWaitEEPCmd(iot, ioh); /* * Write EEPROM from word 0 to word 15 */ for (addr = ASC_EEP_DVC_CFG_BEGIN; addr < ASC_EEP_DVC_CFG_END; addr++, wbuf++) { chksum += *wbuf; ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_DATA, *wbuf); ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_CMD, ASC_EEP_CMD_WRITE | addr); AdvWaitEEPCmd(iot, ioh); DvcSleepMilliSecond(ASC_EEP_DELAY_MS); } /* * Write EEPROM checksum at word 18 */ ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_DATA, chksum); ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_CMD, ASC_EEP_CMD_WRITE | addr); AdvWaitEEPCmd(iot, ioh); wbuf++; /* skip over check_sum */ /* * Write EEPROM OEM name at words 19 to 26 */ for (addr = ASC_EEP_DVC_CTL_BEGIN; addr < ASC_EEP_MAX_WORD_ADDR; addr++, wbuf++) { ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_DATA, *wbuf); ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_CMD, ASC_EEP_CMD_WRITE | addr); AdvWaitEEPCmd(iot, ioh); } ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_EE_CMD, ASC_EEP_CMD_WRITE_DISABLE); AdvWaitEEPCmd(iot, ioh); return; } /* * This function resets the chip and SCSI bus * * It is up to the caller to add a delay to let the bus settle after * calling this function. * * The SCSI_CFG0, SCSI_CFG1, and MEM_CFG registers are set-up in * AdvInitAsc3550Driver(). Here when doing a write to one of these * registers read first and then write. * * Note: A SCSI Bus Reset can not be done until after the EEPROM * configuration is read to determine whether SCSI Bus Resets * should be performed. */ void AdvResetChip(iot, ioh) bus_space_tag_t iot; bus_space_handle_t ioh; { u_int16_t word; u_int8_t byte; /* * Reset Chip. */ ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_CTRL_REG, ADW_CTRL_REG_CMD_RESET); DvcSleepMilliSecond(100); ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_CTRL_REG, ADW_CTRL_REG_CMD_WR_IO_REG); /* * Initialize Chip registers. * * Note: Don't remove the use of a temporary variable in the following * code, otherwise the Microsoft C compiler will turn the following * lines into a no-op. */ byte = ADW_READ_BYTE_REGISTER(iot, ioh, IOPB_MEM_CFG); byte |= RAM_SZ_8KB; ADW_WRITE_BYTE_REGISTER(iot, ioh, IOPB_MEM_CFG, byte); word = ADW_READ_WORD_REGISTER(iot, ioh, IOPW_SCSI_CFG1); word &= ~BIG_ENDIAN; ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_SCSI_CFG1, word); /* * Setting the START_CTL_EMFU 3:2 bits sets a FIFO threshold * of 128 bytes. This register is only accessible to the host. */ ADW_WRITE_BYTE_REGISTER(iot, ioh, IOPB_DMA_CFG0, START_CTL_EMFU | READ_CMD_MRM); } /* * Description: * Send a SCSI request to the ASC3550 chip * * If there is no SG list for the request, set 'sg_entry_cnt' to 0. * * If 'sg_real_addr' is non-zero on entry, AscGetSGList() will not be * called. It is assumed the caller has already initialized 'sg_real_addr'. * * Return: * ADW_SUCCESS(1) - the request is in the mailbox * ADW_BUSY(0) - total request count > 253, try later * ADW_ERROR(-1) - invalid scsi request Q */ int AdvExeScsiQueue(sc, scsiq) ADW_SOFTC *sc; ADW_SCSI_REQ_Q *scsiq; { return AdvSendScsiCmd(sc, scsiq); } /* * Reset SCSI Bus and purge all outstanding requests. * * Return Value: * ADW_TRUE(1) - All requests are purged and SCSI Bus is reset. * * Note: Should always return ADW_TRUE. */ int AdvResetCCB(sc) ADW_SOFTC *sc; { int status; status = AdvSendIdleCmd(sc, (u_int16_t) IDLE_CMD_SCSI_RESET, 0L, 0); AdvResetSCSIBus(sc); return status; } /* * Reset SCSI Bus and delay. */ void AdvResetSCSIBus(sc) ADW_SOFTC *sc; { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int16_t scsi_ctrl; /* * The microcode currently sets the SCSI Bus Reset signal while * handling the AdvSendIdleCmd() IDLE_CMD_SCSI_RESET command above. * But the SCSI Bus Reset Hold Time in the uCode is not deterministic * (it may in fact be for less than the SCSI Spec. minimum of 25 us). * Therefore on return the Adv Library sets the SCSI Bus Reset signal * for ASC_SCSI_RESET_HOLD_TIME_US, which is defined to be greater * than 25 us. */ scsi_ctrl = ADW_READ_WORD_REGISTER(iot, ioh, IOPW_SCSI_CTRL); ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_SCSI_CTRL, scsi_ctrl | ADW_SCSI_CTRL_RSTOUT); DvcDelayMicroSecond((u_int16_t) ASC_SCSI_RESET_HOLD_TIME_US); ADW_WRITE_WORD_REGISTER(iot, ioh, IOPW_SCSI_CTRL, scsi_ctrl & ~ADW_SCSI_CTRL_RSTOUT); DvcSleepMilliSecond((ulong) sc->scsi_reset_wait * 1000); } /* * Adv Library Interrupt Service Routine * * This function is called by a driver's interrupt service routine. * The function disables and re-enables interrupts. * * When a microcode idle command is completed, the ADW_DVC_VAR * 'idle_cmd_done' field is set to ADW_TRUE. * * Note: AdvISR() can be called when interrupts are disabled or even * when there is no hardware interrupt condition present. It will * always check for completed idle commands and microcode requests. * This is an important feature that shouldn't be changed because it * allows commands to be completed from polling mode loops. * * Return: * ADW_TRUE(1) - interrupt was pending * ADW_FALSE(0) - no interrupt was pending */ int AdvISR(sc) ADW_SOFTC *sc; { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int8_t int_stat; u_int16_t next_done_loc, target_bit; int completed_q; ADW_SCSI_REQ_Q *scsiq; ASC_REQ_SENSE *sense_data; int ret; ret = (ADW_IS_INT_PENDING(iot, ioh)) ? ADW_TRUE : ADW_FALSE; /* Reading the register clears the interrupt. */ int_stat = ADW_READ_BYTE_REGISTER(iot, ioh, IOPB_INTR_STATUS_REG); if (int_stat & ADW_INTR_STATUS_INTRB) { sc->idle_cmd_done = ADW_TRUE; } /* * Notify the driver of a hardware detected SCSI Bus Reset. */ if (int_stat & ADW_INTR_STATUS_INTRC) { if (sc->sbreset_callback) { (*(ADW_SBRESET_CALLBACK) sc->sbreset_callback) (sc); } } /* * ASC_MC_HOST_NEXT_DONE (0x129) is actually the last completed RISC * Queue List request. Its forward pointer (RQL_FWD) points to the * current completed RISC Queue List request. */ ADW_READ_BYTE_LRAM(iot, ioh, ASC_MC_HOST_NEXT_DONE, next_done_loc); next_done_loc = ASC_MC_RISC_Q_LIST_BASE + (next_done_loc * ASC_MC_RISC_Q_LIST_SIZE) + RQL_FWD; ADW_READ_BYTE_LRAM(iot, ioh, next_done_loc, completed_q); /* Loop until all completed Q's are processed. */ while (completed_q != ASC_MC_NULL_Q) { ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_HOST_NEXT_DONE, completed_q); next_done_loc = ASC_MC_RISC_Q_LIST_BASE + (completed_q * ASC_MC_RISC_Q_LIST_SIZE); /* * Read the ADW_SCSI_REQ_Q virtual address pointer from * the RISC list entry. The microcode has changed the * ADW_SCSI_REQ_Q physical address to its virtual address. * * Refer to comments at the end of AdvSendScsiCmd() for * more information on the RISC list structure. */ { ushort lsw, msw; ADW_READ_WORD_LRAM(iot, ioh, next_done_loc + RQL_PHYADDR, lsw); ADW_READ_WORD_LRAM(iot, ioh, next_done_loc + RQL_PHYADDR + 2, msw); scsiq = (ADW_SCSI_REQ_Q *) (((u_int32_t) msw << 16) | lsw); } target_bit = ADW_TID_TO_TIDMASK(scsiq->target_id); /* * Clear request microcode control flag. */ scsiq->cntl = 0; /* * Check Condition handling */ if ((scsiq->done_status == QD_WITH_ERROR) && (scsiq->scsi_status == SS_CHK_CONDITION) && (sense_data = (ASC_REQ_SENSE *) scsiq->vsense_addr) != 0 && (scsiq->orig_sense_len - scsiq->sense_len) >= ASC_MIN_SENSE_LEN) { /* * Command returned with a check condition and valid * sense data. */ } /* * If the command that completed was a SCSI INQUIRY and * LUN 0 was sent the command, then process the INQUIRY * command information for the device. */ else if (scsiq->done_status == QD_NO_ERROR && scsiq->cdb[0] == INQUIRY && scsiq->target_lun == 0) { AdvInquiryHandling(sc, scsiq); } /* Change the RISC Queue List state to free. */ ADW_WRITE_BYTE_LRAM(iot, ioh, next_done_loc + RQL_STATE, ASC_MC_QS_FREE); /* Get the RISC Queue List forward pointer. */ ADW_READ_BYTE_LRAM(iot, ioh, next_done_loc + RQL_FWD, completed_q); /* * Notify the driver of the completed request by passing * the ADW_SCSI_REQ_Q pointer to its callback function. */ sc->cur_host_qng--; scsiq->a_flag |= ADW_SCSIQ_DONE; (*(ADW_ISR_CALLBACK) sc->isr_callback) (sc, scsiq); /* * Note: After the driver callback function is called, 'scsiq' * can no longer be referenced. * * Fall through and continue processing other completed * requests... */ } return ret; } /* * Send an idle command to the chip and wait for completion. * * Interrupts do not have to be enabled on entry. * * Return Values: * ADW_TRUE - command completed successfully * ADW_FALSE - command failed */ int AdvSendIdleCmd(sc, idle_cmd, idle_cmd_parameter, flags) ADW_SOFTC *sc; u_int16_t idle_cmd; u_int32_t idle_cmd_parameter; int flags; { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; u_int32_t i; int ret; sc->idle_cmd_done = 0; /* * Write the idle command value after the idle command parameter * has been written to avoid a race condition. If the order is not * followed, the microcode may process the idle command before the * parameters have been written to LRAM. */ ADW_WRITE_DWORD_LRAM(iot, ioh, ASC_MC_IDLE_PARA_STAT, idle_cmd_parameter); ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_IDLE_CMD, idle_cmd); /* * If the 'flags' argument contains the ADW_NOWAIT flag, then * return with success. */ if (flags & ADW_NOWAIT) return ADW_TRUE; for (i = 0; i < SCSI_WAIT_10_SEC * SCSI_MS_PER_SEC; i++) { /* * 'idle_cmd_done' is set by AdvISR(). */ if (sc->idle_cmd_done) break; DvcSleepMilliSecond(1); /* * If interrupts were disabled on entry to AdvSendIdleCmd(), * then they will still be disabled here. Call AdvISR() to * check for the idle command completion. */ (void) AdvISR(sc); } if (sc->idle_cmd_done == ADW_FALSE) { return ADW_FALSE; } else { ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_IDLE_PARA_STAT, ret); return ret; } } /* * Send the SCSI request block to the adapter * * Each of the 255 Adv Library/Microcode RISC Lists or mailboxes has the * following structure: * * 0: RQL_FWD - RISC list forward pointer (1 byte) * 1: RQL_BWD - RISC list backward pointer (1 byte) * 2: RQL_STATE - RISC list state byte - free, ready, done, aborted (1 byte) * 3: RQL_TID - request target id (1 byte) * 4: RQL_PHYADDR - ADW_SCSI_REQ_Q physical pointer (4 bytes) * * Return: * ADW_SUCCESS(1) - the request is in the mailbox * ADW_BUSY(0) - total request count > 253, try later */ static int AdvSendScsiCmd(sc, scsiq) ADW_SOFTC *sc; ADW_SCSI_REQ_Q *scsiq; { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; ADW_CCB *ccb = (ADW_CCB *) scsiq->ccb_ptr; u_int16_t next_ready_loc; u_int8_t next_ready_loc_fwd; long req_size; u_int32_t q_phy_addr; if (sc->cur_host_qng >= sc->max_host_qng) { return ADW_BUSY; } else { sc->cur_host_qng++; } /* * Clear the ADW_SCSI_REQ_Q done flag. */ scsiq->a_flag &= ~ADW_SCSIQ_DONE; /* * Save the original sense buffer length. * * After the request completes 'sense_len' will be set to the residual * byte count of the Auto-Request Sense if a command returns CHECK * CONDITION and the Sense Data is valid indicated by 'host_status' not * being set to QHSTA_M_AUTO_REQ_SENSE_FAIL. To determine the valid * Sense Data Length subtract 'sense_len' from 'orig_sense_len'. */ scsiq->orig_sense_len = scsiq->sense_len; ADW_READ_BYTE_LRAM(iot, ioh, ASC_MC_HOST_NEXT_READY, next_ready_loc); next_ready_loc = ASC_MC_RISC_Q_LIST_BASE + (next_ready_loc * ASC_MC_RISC_Q_LIST_SIZE); /* * Write the physical address of the Q to the mailbox. * We need to skip the first four bytes, because the microcode * uses them internally for linking Q's together. */ req_size = sizeof(ADW_SCSI_REQ_Q); q_phy_addr = sc->sc_dmamap_control->dm_segs[0].ds_addr + ADW_CCB_OFF(ccb) + offsetof(struct adw_ccb, scsiq); scsiq->scsiq_ptr = scsiq; /* * The RISC list structure, which 'next_ready_loc' is a pointer * to in microcode LRAM, has the format detailed in the comment * header for this function. * * Write the ADW_SCSI_REQ_Q physical pointer to * 'next_ready_loc' request. */ ADW_WRITE_DWORD_LRAM(iot, ioh, next_ready_loc + RQL_PHYADDR, q_phy_addr); /* Write target_id to 'next_ready_loc' request. */ ADW_WRITE_BYTE_LRAM(iot, ioh, next_ready_loc + RQL_TID, scsiq->target_id); /* * Set the ASC_MC_HOST_NEXT_READY (0x128) microcode variable to * the 'next_ready_loc' request forward pointer. * * Do this *before* changing the 'next_ready_loc' queue to QS_READY. * After the state is changed to QS_READY 'RQL_FWD' will be changed * by the microcode. * * NOTE: The temporary variable 'next_ready_loc_fwd' is required to * prevent some compilers from optimizing out 'AdvReadByteLram()' if * it were used as the 3rd argument to 'AdvWriteByteLram()'. */ ADW_READ_BYTE_LRAM(iot, ioh, next_ready_loc + RQL_FWD, next_ready_loc_fwd); ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_HOST_NEXT_READY, next_ready_loc_fwd); /* * Change the state of 'next_ready_loc' request from QS_FREE to * QS_READY which will cause the microcode to pick it up and * execute it. * * Can't reference 'next_ready_loc' after changing the request * state to QS_READY. The microcode now owns the request. */ ADW_WRITE_BYTE_LRAM(iot, ioh, next_ready_loc + RQL_STATE, ASC_MC_QS_READY); return ADW_SUCCESS; } /* * Inquiry Information Byte 7 Handling * * Handle SCSI Inquiry Command information for a device by setting * microcode operating variables that affect WDTR, SDTR, and Tag * Queuing. */ static void AdvInquiryHandling(sc, scsiq) ADW_SOFTC *sc; ADW_SCSI_REQ_Q *scsiq; { bus_space_tag_t iot = sc->sc_iot; bus_space_handle_t ioh = sc->sc_ioh; ASC_SCSI_INQUIRY *inq; u_int16_t cfg_word; u_int16_t tidmask; u_int8_t tid; /* * AdvInquiryHandling() requires up to INQUIRY information Byte 7 * to be available. * * If less than 8 bytes of INQUIRY information were requested or less * than 8 bytes were transferred, then return. cdb[4] is the request * length and the ADW_SCSI_REQ_Q 'data_cnt' field is set by the * microcode to the transfer residual count. */ if (scsiq->cdb[4] < 8 || (scsiq->cdb[4] - scsiq->data_cnt) < 8) { return; } tid = scsiq->target_id; inq = (ASC_SCSI_INQUIRY *) scsiq->vdata_addr; /* * WDTR, SDTR, and Tag Queuing cannot be enabled for old devices. */ if (inq->byte3.rsp_data_fmt < 2 && inq->byte2.ansi_apr_ver < 2) { return; } else { /* * INQUIRY Byte 7 Handling * * Use a device's INQUIRY byte 7 to determine whether it * supports WDTR, SDTR, and Tag Queuing. If the feature * is enabled in the EEPROM and the device supports the * feature, then enable it in the microcode. */ tidmask = ADW_TID_TO_TIDMASK(tid); /* * Wide Transfers * * If the EEPROM enabled WDTR for the device and the device * supports wide bus (16 bit) transfers, then turn on the * device's 'wdtr_able' bit and write the new value to the * microcode. */ if ((sc->wdtr_able & tidmask) && inq->byte7.WBus16) { ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_WDTR_ABLE, cfg_word); if ((cfg_word & tidmask) == 0) { cfg_word |= tidmask; ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_WDTR_ABLE, cfg_word); /* * Clear the microcode "WDTR negotiation" done * indicator for the target to cause it * to negotiate with the new setting set above. */ ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_WDTR_DONE, cfg_word); cfg_word &= ~tidmask; ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_WDTR_DONE, cfg_word); } } /* * Synchronous Transfers * * If the EEPROM enabled SDTR for the device and the device * supports synchronous transfers, then turn on the device's * 'sdtr_able' bit. Write the new value to the microcode. */ if ((sc->sdtr_able & tidmask) && inq->byte7.Sync) { ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_SDTR_ABLE, cfg_word); if ((cfg_word & tidmask) == 0) { cfg_word |= tidmask; ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_SDTR_ABLE, cfg_word); /* * Clear the microcode "SDTR negotiation" done * indicator for the target to cause it * to negotiate with the new setting set above. */ ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_SDTR_DONE, cfg_word); cfg_word &= ~tidmask; ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_SDTR_DONE, cfg_word); } } /* * If the EEPROM enabled Tag Queuing for device and the * device supports Tag Queuing, then turn on the device's * 'tagqng_enable' bit in the microcode and set the microcode * maximum command count to the ADW_DVC_VAR 'max_dvc_qng' * value. * * Tag Queuing is disabled for the BIOS which runs in polled * mode and would see no benefit from Tag Queuing. Also by * disabling Tag Queuing in the BIOS devices with Tag Queuing * bugs will at least work with the BIOS. */ if ((sc->tagqng_able & tidmask) && inq->byte7.CmdQue) { ADW_READ_WORD_LRAM(iot, ioh, ASC_MC_TAGQNG_ABLE, cfg_word); cfg_word |= tidmask; ADW_WRITE_WORD_LRAM(iot, ioh, ASC_MC_TAGQNG_ABLE, cfg_word); ADW_WRITE_BYTE_LRAM(iot, ioh, ASC_MC_NUMBER_OF_MAX_CMD + tid, sc->max_dvc_qng); } } } static void DvcSleepMilliSecond(n) ulong n; { DELAY(n * 1000); } static void DvcDelayMicroSecond(n) ulong n; { DELAY(n); }