NetBSD/sys/dev/ic/aic7xxx_seeprom.c
soren 3b8b24cffe Only wait 100 ms instead of 1 s for the SEEPROM to come ready. This is
still plenty for cards with SEEPROM's and reduces the delay on platforms
without, which conveniently also avoids triggering the O2 watchdog timer.
2000-06-06 17:29:40 +00:00

669 lines
18 KiB
C

/* $NetBSD: aic7xxx_seeprom.c,v 1.3 2000/06/06 17:29:40 soren 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 <thorpej@netbsd.org>.
*
* 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 <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/kernel.h>
#include <sys/queue.h>
#include <sys/device.h>
#include <machine/bus.h>
#include <machine/intr.h>
#include <dev/scsipi/scsi_all.h>
#include <dev/scsipi/scsipi_all.h>
#include <dev/scsipi/scsiconf.h>
#include <dev/microcode/aic7xxx/aic7xxx_reg.h>
#include <dev/ic/aic7xxxvar.h>
#include <dev/ic/smc93cx6var.h>
#ifdef DEBUG
#define bootverbose 1
#else
#define bootverbose 0
#endif
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 *)&sc;
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(sd)
struct seeprom_descriptor *sd;
{
/* Release access to the memory port and the serial EEPROM. */
SEEPROM_OUTB(sd, 0);
}
static void
write_brdctl(ahc, value)
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(ahc)
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);
}