/* $NetBSD: aic7xxx_seeprom.c,v 1.6 2001/11/13 13:14:34 lukem Exp $ */ /* * Product specific probe and attach routines for: * 3940, 2940, aic7895, aic7890, aic7880, * aic7870, aic7860 and aic7850 SCSI controllers * * These are the SEEPROM-reading functions only. They were split off from * the PCI-specific support by Jason R. Thorpe . * * Copyright (c) 1994, 1995, 1996, 1997, 1998, 1999, 2000 Justin T. Gibbs. * All rights reserved. * * 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, * without modification. * 2. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Alternatively, this software may be distributed under the terms of the * the GNU Public License ("GPL"). * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. * * $FreeBSD: src/sys/dev/aic7xxx/ahc_pci.c,v 1.27 2000/01/10 01:47:51 gibbs Exp $ */ #include __KERNEL_RCSID(0, "$NetBSD: aic7xxx_seeprom.c,v 1.6 2001/11/13 13:14:34 lukem Exp $"); #include #include #include #include #include #include #include /* for AB_* needed by bootverbose */ #include #include #include #include #include #include #include #include static void configure_termination(struct ahc_softc *, struct seeprom_descriptor *, u_int, u_int *); static void ahc_new_term_detect(struct ahc_softc *, int *, int *, int *, int *, int *); static void aic787X_cable_detect(struct ahc_softc *, int *, int *, int *, int *); static void aic785X_cable_detect(struct ahc_softc *, int *, int *, int *); static int acquire_seeprom(struct ahc_softc *, struct seeprom_descriptor *); static void release_seeprom(struct seeprom_descriptor *); static void write_brdctl(struct ahc_softc *, u_int8_t); static u_int8_t read_brdctl(struct ahc_softc *); /* * Check the external port logic for a serial eeprom * and termination/cable detection contrls. */ void check_extport(struct ahc_softc *ahc, u_int *sxfrctl1) { struct seeprom_descriptor sd; struct seeprom_config sc; u_int scsi_conf; u_int adapter_control; int have_seeprom; int have_autoterm; sd.sd_tag = ahc->tag; sd.sd_bsh = ahc->bsh; sd.sd_control_offset = SEECTL; sd.sd_status_offset = SEECTL; sd.sd_dataout_offset = SEECTL; /* * For some multi-channel devices, the c46 is simply too * small to work. For the other controller types, we can * get our information from either SEEPROM type. Set the * type to start our probe with accordingly. */ if (ahc->flags & AHC_LARGE_SEEPROM) sd.sd_chip = C56_66; else sd.sd_chip = C46; sd.sd_MS = SEEMS; sd.sd_RDY = SEERDY; sd.sd_CS = SEECS; sd.sd_CK = SEECK; sd.sd_DO = SEEDO; sd.sd_DI = SEEDI; have_seeprom = acquire_seeprom(ahc, &sd); if (have_seeprom) { if (bootverbose) printf("%s: Reading SEEPROM...", ahc_name(ahc)); for (;;) { bus_size_t start_addr; start_addr = 32 * (ahc->channel - 'A'); have_seeprom = read_seeprom(&sd, (u_int16_t *)&sc, start_addr, sizeof(sc)/2); if (have_seeprom) { /* Check checksum */ int i; int maxaddr; u_int32_t checksum; u_int16_t *scarray; maxaddr = (sizeof(sc)/2) - 1; checksum = 0; scarray = (u_int16_t *)≻ for (i = 0; i < maxaddr; i++) checksum = checksum + scarray[i]; if (checksum == 0 || (checksum & 0xFFFF) != sc.checksum) { if (bootverbose && sd.sd_chip == C56_66) printf ("checksum error\n"); have_seeprom = 0; } else { if (bootverbose) printf("done.\n"); break; } } if (sd.sd_chip == C56_66) break; sd.sd_chip = C56_66; } } if (!have_seeprom) { if (bootverbose) printf("%s: No SEEPROM available\n", ahc_name(ahc)); ahc->flags |= AHC_USEDEFAULTS; } else { /* * Put the data we've collected down into SRAM * where ahc_init will find it. */ int i; int max_targ = sc.max_targets & CFMAXTARG; u_int16_t discenable; u_int16_t ultraenb; discenable = 0; ultraenb = 0; if ((sc.adapter_control & CFULTRAEN) != 0) { /* * Determine if this adapter has a "newstyle" * SEEPROM format. */ for (i = 0; i < max_targ; i++) { if ((sc.device_flags[i] & CFSYNCHISULTRA) != 0){ ahc->flags |= AHC_NEWEEPROM_FMT; break; } } } for (i = 0; i < max_targ; i++) { u_int scsirate; u_int16_t target_mask; target_mask = 0x01 << i; if (sc.device_flags[i] & CFDISC) discenable |= target_mask; if ((ahc->flags & AHC_NEWEEPROM_FMT) != 0) { if ((sc.device_flags[i] & CFSYNCHISULTRA) != 0) ultraenb |= target_mask; } else if ((sc.adapter_control & CFULTRAEN) != 0) { ultraenb |= target_mask; } if ((sc.device_flags[i] & CFXFER) == 0x04 && (ultraenb & target_mask) != 0) { /* Treat 10MHz as a non-ultra speed */ sc.device_flags[i] &= ~CFXFER; ultraenb &= ~target_mask; } if ((ahc->features & AHC_ULTRA2) != 0) { u_int offset; if (sc.device_flags[i] & CFSYNCH) offset = MAX_OFFSET_ULTRA2; else offset = 0; ahc_outb(ahc, TARG_OFFSET + i, offset); scsirate = (sc.device_flags[i] & CFXFER) | ((ultraenb & target_mask) ? 0x8 : 0x0); if (sc.device_flags[i] & CFWIDEB) scsirate |= WIDEXFER; } else { scsirate = (sc.device_flags[i] & CFXFER) << 4; if (sc.device_flags[i] & CFSYNCH) scsirate |= SOFS; if (sc.device_flags[i] & CFWIDEB) scsirate |= WIDEXFER; } ahc_outb(ahc, TARG_SCSIRATE + i, scsirate); } ahc->our_id = sc.brtime_id & CFSCSIID; scsi_conf = (ahc->our_id & 0x7); if (sc.adapter_control & CFSPARITY) scsi_conf |= ENSPCHK; if (sc.adapter_control & CFRESETB) scsi_conf |= RESET_SCSI; if (sc.bios_control & CFEXTEND) ahc->flags |= AHC_EXTENDED_TRANS_A; if (ahc->features & AHC_ULTRA && (ahc->flags & AHC_NEWEEPROM_FMT) == 0) { /* Should we enable Ultra mode? */ if (!(sc.adapter_control & CFULTRAEN)) /* Treat us as a non-ultra card */ ultraenb = 0; } /* Set SCSICONF info */ ahc_outb(ahc, SCSICONF, scsi_conf); ahc_outb(ahc, DISC_DSB, ~(discenable & 0xff)); ahc_outb(ahc, DISC_DSB + 1, ~((discenable >> 8) & 0xff)); ahc_outb(ahc, ULTRA_ENB, ultraenb & 0xff); ahc_outb(ahc, ULTRA_ENB + 1, (ultraenb >> 8) & 0xff); } /* * Cards that have the external logic necessary to talk to * a SEEPROM, are almost certain to have the remaining logic * necessary for auto-termination control. This assumption * hasn't failed yet... */ have_autoterm = have_seeprom; if (have_seeprom) adapter_control = sc.adapter_control; else adapter_control = CFAUTOTERM; /* * Some low-cost chips have SEEPROM and auto-term control built * in, instead of using a GAL. They can tell us directly * if the termination logic is enabled. */ if ((ahc->features & AHC_SPIOCAP) != 0) { if ((ahc_inb(ahc, SPIOCAP) & SSPIOCPS) != 0) have_autoterm = TRUE; else have_autoterm = FALSE; } if (have_autoterm) configure_termination(ahc, &sd, adapter_control, sxfrctl1); release_seeprom(&sd); } static void configure_termination(struct ahc_softc *ahc, struct seeprom_descriptor *sd, u_int adapter_control, u_int *sxfrctl1) { u_int8_t brddat; brddat = 0; /* * Update the settings in sxfrctl1 to match the * termination settings */ *sxfrctl1 = 0; /* * SEECS must be on for the GALS to latch * the data properly. Be sure to leave MS * on or we will release the seeprom. */ SEEPROM_OUTB(sd, sd->sd_MS | sd->sd_CS); if ((adapter_control & CFAUTOTERM) != 0 || (ahc->features & AHC_NEW_TERMCTL) != 0) { int internal50_present; int internal68_present; int externalcable_present; int eeprom_present; int enableSEC_low; int enableSEC_high; int enablePRI_low; int enablePRI_high; enableSEC_low = 0; enableSEC_high = 0; enablePRI_low = 0; enablePRI_high = 0; if ((ahc->features & AHC_NEW_TERMCTL) != 0) { ahc_new_term_detect(ahc, &enableSEC_low, &enableSEC_high, &enablePRI_low, &enablePRI_high, &eeprom_present); if ((adapter_control & CFSEAUTOTERM) == 0) { if (bootverbose) printf("%s: Manual SE Termination\n", ahc_name(ahc)); enableSEC_low = (adapter_control & CFSTERM); enableSEC_high = (adapter_control & CFWSTERM); } if ((adapter_control & CFAUTOTERM) == 0) { if (bootverbose) printf("%s: Manual LVD Termination\n", ahc_name(ahc)); enablePRI_low = enablePRI_high = (adapter_control & CFLVDSTERM); } /* Make the table calculations below happy */ internal50_present = 0; internal68_present = 1; externalcable_present = 1; } else if ((ahc->features & AHC_SPIOCAP) != 0) { aic785X_cable_detect(ahc, &internal50_present, &externalcable_present, &eeprom_present); } else { aic787X_cable_detect(ahc, &internal50_present, &internal68_present, &externalcable_present, &eeprom_present); } if ((ahc->features & AHC_WIDE) == 0) internal68_present = 0; if (bootverbose) { if ((ahc->features & AHC_ULTRA2) == 0) { printf("%s: internal 50 cable %s present, " "internal 68 cable %s present\n", ahc_name(ahc), internal50_present ? "is":"not", internal68_present ? "is":"not"); printf("%s: external cable %s present\n", ahc_name(ahc), externalcable_present ? "is":"not"); } printf("%s: BIOS eeprom %s present\n", ahc_name(ahc), eeprom_present ? "is" : "not"); } if ((ahc->flags & AHC_INT50_SPEEDFLEX) != 0) { /* * The 50 pin connector is a separate bus, * so force it to always be terminated. * In the future, perform current sensing * to determine if we are in the middle of * a properly terminated bus. */ internal50_present = 0; } /* * Now set the termination based on what * we found. * Flash Enable = BRDDAT7 * Secondary High Term Enable = BRDDAT6 * Secondary Low Term Enable = BRDDAT5 (7890) * Primary High Term Enable = BRDDAT4 (7890) */ if ((ahc->features & AHC_ULTRA2) == 0 && (internal50_present != 0) && (internal68_present != 0) && (externalcable_present != 0)) { printf("%s: Illegal cable configuration!!. " "Only two connectors on the " "adapter may be used at a " "time!\n", ahc_name(ahc)); } if ((ahc->features & AHC_WIDE) != 0 && ((externalcable_present == 0) || (internal68_present == 0) || (enableSEC_high != 0))) { brddat |= BRDDAT6; if (bootverbose) { if ((ahc->flags & AHC_INT50_SPEEDFLEX) != 0) printf("%s: 68 pin termination " "Enabled\n", ahc_name(ahc)); else printf("%s: %sHigh byte termination " "Enabled\n", ahc_name(ahc), enableSEC_high ? "Secondary " : ""); } } if (((internal50_present ? 1 : 0) + (internal68_present ? 1 : 0) + (externalcable_present ? 1 : 0)) <= 1 || (enableSEC_low != 0)) { if ((ahc->features & AHC_ULTRA2) != 0) brddat |= BRDDAT5; else *sxfrctl1 |= STPWEN; if (bootverbose) { if ((ahc->flags & AHC_INT50_SPEEDFLEX) != 0) printf("%s: 50 pin termination " "Enabled\n", ahc_name(ahc)); else printf("%s: %sLow byte termination " "Enabled\n", ahc_name(ahc), enableSEC_low ? "Secondary " : ""); } } if (enablePRI_low != 0) { *sxfrctl1 |= STPWEN; if (bootverbose) printf("%s: Primary Low Byte termination " "Enabled\n", ahc_name(ahc)); } /* * Setup STPWEN before setting up the rest of * the termination per the tech note on the U160 cards. */ ahc_outb(ahc, SXFRCTL1, *sxfrctl1); if (enablePRI_high != 0) { brddat |= BRDDAT4; if (bootverbose) printf("%s: Primary High Byte " "termination Enabled\n", ahc_name(ahc)); } write_brdctl(ahc, brddat); } else { if ((adapter_control & CFSTERM) != 0) { *sxfrctl1 |= STPWEN; if (bootverbose) printf("%s: %sLow byte termination Enabled\n", ahc_name(ahc), (ahc->features & AHC_ULTRA2) ? "Primary " : ""); } if ((adapter_control & CFWSTERM) != 0) { brddat |= BRDDAT6; if (bootverbose) printf("%s: %sHigh byte termination Enabled\n", ahc_name(ahc), (ahc->features & AHC_ULTRA2) ? "Secondary " : ""); } /* * Setup STPWEN before setting up the rest of * the termination per the tech note on the U160 cards. */ ahc_outb(ahc, SXFRCTL1, *sxfrctl1); write_brdctl(ahc, brddat); } SEEPROM_OUTB(sd, sd->sd_MS); /* Clear CS */ } static void ahc_new_term_detect(struct ahc_softc *ahc, int *enableSEC_low, int *enableSEC_high, int *enablePRI_low, int *enablePRI_high, int *eeprom_present) { u_int8_t brdctl; /* * BRDDAT7 = Eeprom * BRDDAT6 = Enable Secondary High Byte termination * BRDDAT5 = Enable Secondary Low Byte termination * BRDDAT4 = Enable Primary high byte termination * BRDDAT3 = Enable Primary low byte termination */ brdctl = read_brdctl(ahc); *eeprom_present = brdctl & BRDDAT7; *enableSEC_high = (brdctl & BRDDAT6); *enableSEC_low = (brdctl & BRDDAT5); *enablePRI_high = (brdctl & BRDDAT4); *enablePRI_low = (brdctl & BRDDAT3); } static void aic787X_cable_detect(struct ahc_softc *ahc, int *internal50_present, int *internal68_present, int *externalcable_present, int *eeprom_present) { u_int8_t brdctl; /* * First read the status of our cables. * Set the rom bank to 0 since the * bank setting serves as a multiplexor * for the cable detection logic. * BRDDAT5 controls the bank switch. */ write_brdctl(ahc, 0); /* * Now read the state of the internal * connectors. BRDDAT6 is INT50 and * BRDDAT7 is INT68. */ brdctl = read_brdctl(ahc); *internal50_present = !(brdctl & BRDDAT6); *internal68_present = !(brdctl & BRDDAT7); /* * Set the rom bank to 1 and determine * the other signals. */ write_brdctl(ahc, BRDDAT5); /* * Now read the state of the external * connectors. BRDDAT6 is EXT68 and * BRDDAT7 is EPROMPS. */ brdctl = read_brdctl(ahc); *externalcable_present = !(brdctl & BRDDAT6); *eeprom_present = brdctl & BRDDAT7; } static void aic785X_cable_detect(struct ahc_softc *ahc, int *internal50_present, int *externalcable_present, int *eeprom_present) { u_int8_t brdctl; ahc_outb(ahc, BRDCTL, BRDRW|BRDCS); ahc_outb(ahc, BRDCTL, 0); brdctl = ahc_inb(ahc, BRDCTL); *internal50_present = !(brdctl & BRDDAT5); *externalcable_present = !(brdctl & BRDDAT6); *eeprom_present = (ahc_inb(ahc, SPIOCAP) & EEPROM) != 0; } static int acquire_seeprom(struct ahc_softc *ahc, struct seeprom_descriptor *sd) { int wait; if ((ahc->features & AHC_SPIOCAP) != 0 && (ahc_inb(ahc, SPIOCAP) & SEEPROM) == 0) return (0); /* * Request access of the memory port. When access is * granted, SEERDY will go high. We use a 100 msec * timeout which should be near 100 msecs more than * is needed. Reason: after the chip reset, there * should be no contention. */ SEEPROM_OUTB(sd, sd->sd_MS); wait = 100; /* 100 msec timeout */ while (--wait && ((SEEPROM_STATUS_INB(sd) & sd->sd_RDY) == 0)) { DELAY(1000); /* delay 1 msec */ } if ((SEEPROM_STATUS_INB(sd) & sd->sd_RDY) == 0) { SEEPROM_OUTB(sd, 0); return (0); } return(1); } static void release_seeprom(struct seeprom_descriptor *sd) { /* Release access to the memory port and the serial EEPROM. */ SEEPROM_OUTB(sd, 0); } static void write_brdctl(struct ahc_softc *ahc, u_int8_t value) { u_int8_t brdctl; if ((ahc->chip & AHC_CHIPID_MASK) == AHC_AIC7895) { brdctl = BRDSTB; if (ahc->channel == 'B') brdctl |= BRDCS; } else if ((ahc->features & AHC_ULTRA2) != 0) { brdctl = 0; } else { brdctl = BRDSTB|BRDCS; } ahc_outb(ahc, BRDCTL, brdctl); DELAY(20); brdctl |= value; ahc_outb(ahc, BRDCTL, brdctl); DELAY(20); if ((ahc->features & AHC_ULTRA2) != 0) brdctl |= BRDSTB_ULTRA2; else brdctl &= ~BRDSTB; ahc_outb(ahc, BRDCTL, brdctl); DELAY(20); if ((ahc->features & AHC_ULTRA2) != 0) brdctl = 0; else brdctl &= ~BRDCS; ahc_outb(ahc, BRDCTL, brdctl); } static u_int8_t read_brdctl(struct ahc_softc *ahc) { u_int8_t brdctl; u_int8_t value; if ((ahc->chip & AHC_CHIPID_MASK) == AHC_AIC7895) { brdctl = BRDRW; if (ahc->channel == 'B') brdctl |= BRDCS; } else if ((ahc->features & AHC_ULTRA2) != 0) { brdctl = BRDRW_ULTRA2; } else { brdctl = BRDRW|BRDCS; } ahc_outb(ahc, BRDCTL, brdctl); DELAY(20); value = ahc_inb(ahc, BRDCTL); ahc_outb(ahc, BRDCTL, 0); return (value); }