summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Kettenis <kettenis@cvs.openbsd.org>2010-06-29 18:57:54 +0000
committerMark Kettenis <kettenis@cvs.openbsd.org>2010-06-29 18:57:54 +0000
commitd4c0bd22338527501a75ffd7423897d32ba72409 (patch)
tree7b011200cba2c7765488142b53ddadb361c99e81
parent710f19eb9c2212563a6331b2f049de0ead8e0e06 (diff)
Add code to make ahci(4) suspend/resume properly. Probably not perfect yet,
but it seems to work reliably on several laptops. ok dlg@ (a while ago), tested by mlarkin@ and marco@
-rw-r--r--sys/dev/pci/ahci.c192
1 files changed, 182 insertions, 10 deletions
diff --git a/sys/dev/pci/ahci.c b/sys/dev/pci/ahci.c
index 4f0b095fcb3..ee10dbc2c13 100644
--- a/sys/dev/pci/ahci.c
+++ b/sys/dev/pci/ahci.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: ahci.c,v 1.165 2010/05/24 04:40:14 dlg Exp $ */
+/* $OpenBSD: ahci.c,v 1.166 2010/06/29 18:57:53 kettenis Exp $ */
/*
* Copyright (c) 2006 David Gwynne <dlg@openbsd.org>
@@ -401,6 +401,8 @@ struct ahci_softc {
struct atascsi *sc_atascsi;
+ u_int32_t sc_cap;
+
#ifdef AHCI_COALESCE
u_int32_t sc_ccc_mask;
u_int32_t sc_ccc_ports;
@@ -463,12 +465,14 @@ int ahci_pci_match(struct device *, void *, void *);
void ahci_pci_attach(struct device *, struct device *,
void *);
int ahci_pci_detach(struct device *, int);
+int ahci_pci_activate(struct device *, int);
struct cfattach ahci_pci_ca = {
sizeof(struct ahci_softc),
ahci_pci_match,
ahci_pci_attach,
- ahci_pci_detach
+ ahci_pci_detach,
+ ahci_pci_activate
};
struct cfattach ahci_jmb_ca = {
@@ -493,6 +497,7 @@ void ahci_unmap_intr(struct ahci_softc *);
int ahci_init(struct ahci_softc *);
int ahci_port_alloc(struct ahci_softc *, u_int);
void ahci_port_free(struct ahci_softc *, u_int);
+int ahci_port_init(struct ahci_softc *, u_int);
int ahci_port_start(struct ahci_port *, int);
int ahci_port_stop(struct ahci_port *, int);
@@ -669,7 +674,7 @@ ahci_pci_attach(struct device *parent, struct device *self, void *aux)
struct atascsi_attach_args aaa;
const struct ahci_device *ad;
pci_intr_handle_t ih;
- u_int32_t cap, pi;
+ u_int32_t pi;
int i;
sc->sc_pc = pa->pa_pc;
@@ -707,13 +712,13 @@ ahci_pci_attach(struct device *parent, struct device *self, void *aux)
sc->sc_dmat = pa->pa_dmat;
- cap = ahci_read(sc, AHCI_REG_CAP);
- sc->sc_ncmds = AHCI_REG_CAP_NCS(cap);
+ sc->sc_cap = ahci_read(sc, AHCI_REG_CAP);
+ sc->sc_ncmds = AHCI_REG_CAP_NCS(sc->sc_cap);
#ifdef AHCI_DEBUG
if (ahcidebug & AHCI_D_VERBOSE) {
const char *gen;
- switch (cap & AHCI_REG_CAP_ISS) {
+ switch (sc->sc_cap & AHCI_REG_CAP_ISS) {
case AHCI_REG_CAP_ISS_G1:
gen = "1 (1.5Gbps)";
break;
@@ -726,8 +731,8 @@ ahci_pci_attach(struct device *parent, struct device *self, void *aux)
}
printf("%s: capabilities 0x%b, %d ports, %d cmds, gen %s\n",
- DEVNAME(sc), cap, AHCI_FMT_CAP,
- AHCI_REG_CAP_NP(cap), sc->sc_ncmds, gen);
+ DEVNAME(sc), sc->sc_cap, AHCI_FMT_CAP,
+ AHCI_REG_CAP_NP(sc->sc_cap), sc->sc_ncmds, gen);
}
#endif
@@ -737,7 +742,7 @@ ahci_pci_attach(struct device *parent, struct device *self, void *aux)
#ifdef AHCI_COALESCE
/* Naive coalescing support - enable for all ports. */
- if (cap & AHCI_REG_CAP_CCCS) {
+ if (sc->sc_cap & AHCI_REG_CAP_CCCS) {
u_int16_t ccc_timeout = 20;
u_int8_t ccc_numcomplete = 12;
u_int32_t ccc_ctl;
@@ -787,7 +792,8 @@ noccc:
aaa.aaa_nports = AHCI_MAX_PORTS;
aaa.aaa_ncmds = sc->sc_ncmds;
aaa.aaa_capability = ASAA_CAP_NEEDS_RESERVED;
- if (!(sc->sc_flags & AHCI_F_NO_NCQ) && (cap & AHCI_REG_CAP_SNCQ))
+ if (!(sc->sc_flags & AHCI_F_NO_NCQ) &&
+ (sc->sc_cap & AHCI_REG_CAP_SNCQ))
aaa.aaa_capability |= ASAA_CAP_NCQ;
sc->sc_atascsi = atascsi_attach(&sc->sc_dev, &aaa);
@@ -833,6 +839,42 @@ ahci_pci_detach(struct device *self, int flags)
}
int
+ahci_pci_activate(struct device *self, int act)
+{
+ struct ahci_softc *sc = (struct ahci_softc *)self;
+ int i, rv = 0;
+
+ switch (act) {
+ case DVACT_SUSPEND:
+ rv = config_activate_children(self, act);
+ for (i = 0; i < AHCI_MAX_PORTS; i++) {
+ if (sc->sc_ports[i] != NULL)
+ ahci_port_stop(sc->sc_ports[i], 1);
+ }
+ break;
+ case DVACT_RESUME:
+ /* enable ahci (global interrupts disabled) */
+ ahci_write(sc, AHCI_REG_GHC, AHCI_REG_GHC_AE);
+
+ /* restore BIOS initialised parameters */
+ ahci_write(sc, AHCI_REG_CAP, sc->sc_cap);
+
+ for (i = 0; i < AHCI_MAX_PORTS; i++) {
+ if (sc->sc_ports[i] != NULL)
+ ahci_port_init(sc, i);
+ }
+
+ /* Enable interrupts */
+ ahci_write(sc, AHCI_REG_GHC, AHCI_REG_GHC_AE | AHCI_REG_GHC_IE);
+
+ rv = config_activate_children(self, act);
+ break;
+ }
+
+ return (rv);
+}
+
+int
ahci_map_regs(struct ahci_softc *sc, struct pci_attach_args *pa)
{
pcireg_t maptype;
@@ -1186,6 +1228,135 @@ ahci_port_free(struct ahci_softc *sc, u_int port)
}
int
+ahci_port_init(struct ahci_softc *sc, u_int port)
+{
+ struct ahci_port *ap;
+ u_int64_t dva;
+ u_int32_t cmd;
+ int rc = ENOMEM;
+
+#ifdef AHCI_DEBUG
+ snprintf(ap->ap_name, sizeof(ap->ap_name), "%s.%d",
+ DEVNAME(sc), port);
+#endif
+ ap = sc->sc_ports[port];
+
+ /* Disable port interrupts */
+ ahci_pwrite(ap, AHCI_PREG_IE, 0);
+
+ /* Sec 10.1.2 - deinitialise port if it is already running */
+ cmd = ahci_pread(ap, AHCI_PREG_CMD);
+ if (ISSET(cmd, (AHCI_PREG_CMD_ST | AHCI_PREG_CMD_CR |
+ AHCI_PREG_CMD_FRE | AHCI_PREG_CMD_FR)) ||
+ ISSET(ahci_pread(ap, AHCI_PREG_SCTL), AHCI_PREG_SCTL_DET)) {
+ int r;
+
+ r = ahci_port_stop(ap, 1);
+ if (r) {
+ printf("%s: unable to disable %s, ignoring port %d\n",
+ DEVNAME(sc), r == 2 ? "CR" : "FR", port);
+ rc = ENXIO;
+ goto reterr;
+ }
+
+ /* Write DET to zero */
+ ahci_pwrite(ap, AHCI_PREG_SCTL, 0);
+ }
+
+ /* Setup RFIS base address */
+ ap->ap_rfis = (struct ahci_rfis *) AHCI_DMA_KVA(ap->ap_dmamem_rfis);
+ dva = AHCI_DMA_DVA(ap->ap_dmamem_rfis);
+ ahci_pwrite(ap, AHCI_PREG_FBU, (u_int32_t)(dva >> 32));
+ ahci_pwrite(ap, AHCI_PREG_FB, (u_int32_t)dva);
+
+ /* Enable FIS reception and activate port. */
+ cmd = ahci_pread(ap, AHCI_PREG_CMD) & ~AHCI_PREG_CMD_ICC;
+ cmd |= AHCI_PREG_CMD_FRE | AHCI_PREG_CMD_POD | AHCI_PREG_CMD_SUD;
+ ahci_pwrite(ap, AHCI_PREG_CMD, cmd | AHCI_PREG_CMD_ICC_ACTIVE);
+
+ /* Check whether port activated. Skip it if not. */
+ cmd = ahci_pread(ap, AHCI_PREG_CMD) & ~AHCI_PREG_CMD_ICC;
+ if (!ISSET(cmd, AHCI_PREG_CMD_FRE)) {
+ rc = ENXIO;
+ goto reterr;
+ }
+
+ /* Setup command list base address */
+ dva = AHCI_DMA_DVA(ap->ap_dmamem_cmd_list);
+ ahci_pwrite(ap, AHCI_PREG_CLBU, (u_int32_t)(dva >> 32));
+ ahci_pwrite(ap, AHCI_PREG_CLB, (u_int32_t)dva);
+
+ /* Wait for ICC change to complete */
+ ahci_pwait_clr(ap, AHCI_PREG_CMD, AHCI_PREG_CMD_ICC);
+
+ /* Reset port */
+ rc = ahci_port_portreset(ap);
+ switch (rc) {
+ case ENODEV:
+ switch (ahci_pread(ap, AHCI_PREG_SSTS) & AHCI_PREG_SSTS_DET) {
+ case AHCI_PREG_SSTS_DET_DEV_NE:
+ printf("%s: device not communicating on port %d\n",
+ DEVNAME(sc), port);
+ break;
+ case AHCI_PREG_SSTS_DET_PHYOFFLINE:
+ printf("%s: PHY offline on port %d\n", DEVNAME(sc),
+ port);
+ break;
+ default:
+ DPRINTF(AHCI_D_VERBOSE, "%s: no device detected "
+ "on port %d\n", DEVNAME(sc), port);
+ break;
+ }
+ goto reterr;
+
+ case EBUSY:
+ printf("%s: device on port %d didn't come ready, "
+ "TFD: 0x%b\n", DEVNAME(sc), port,
+ ahci_pread(ap, AHCI_PREG_TFD), AHCI_PFMT_TFD_STS);
+
+ /* Try a soft reset to clear busy */
+ rc = ahci_port_softreset(ap);
+ if (rc) {
+ printf("%s: unable to communicate "
+ "with device on port %d\n", DEVNAME(sc), port);
+ goto reterr;
+ }
+ break;
+
+ default:
+ break;
+ }
+ DPRINTF(AHCI_D_VERBOSE, "%s: detected device on port %d\n",
+ DEVNAME(sc), port);
+
+ /* Enable command transfers on port */
+ if (ahci_port_start(ap, 0)) {
+ printf("%s: failed to start command DMA on port %d, "
+ "disabling\n", DEVNAME(sc), port);
+ rc = ENXIO; /* couldn't start port */
+ }
+
+ /* Flush interrupts for port */
+ ahci_pwrite(ap, AHCI_PREG_IS, ahci_pread(ap, AHCI_PREG_IS));
+ ahci_write(sc, AHCI_REG_IS, 1 << port);
+
+ /* Enable port interrupts */
+ ahci_pwrite(ap, AHCI_PREG_IE, AHCI_PREG_IE_TFEE | AHCI_PREG_IE_HBFE |
+ AHCI_PREG_IE_IFE | AHCI_PREG_IE_OFE | AHCI_PREG_IE_DPE |
+ AHCI_PREG_IE_UFE |
+#ifdef AHCI_COALESCE
+ ((sc->sc_ccc_ports & (1 << port)) ? 0 : (AHCI_PREG_IE_SDBE |
+ AHCI_PREG_IE_DHRE))
+#else
+ AHCI_PREG_IE_SDBE | AHCI_PREG_IE_DHRE
+#endif
+ );
+
+reterr:
+ return (rc);
+}
+
+int
ahci_port_start(struct ahci_port *ap, int fre_only)
{
u_int32_t r;
@@ -1429,6 +1600,7 @@ ahci_port_portreset(struct ahci_port *ap)
/* Clear SERR (incl X bit), so TFD can update */
ahci_pwrite(ap, AHCI_PREG_SERR, ahci_pread(ap, AHCI_PREG_SERR));
+ delay(1000000);
/* Wait for device to become ready */
/* XXX maybe more than the default wait is appropriate here? */