summaryrefslogtreecommitdiff
path: root/sys/dev
diff options
context:
space:
mode:
authoraugustss <augustss@NetBSD.org>2001-11-16 01:57:08 +0000
committeraugustss <augustss@NetBSD.org>2001-11-16 01:57:08 +0000
commit07ea2e28cb85314971501bf32d2db8bb3944c003 (patch)
treeb797711de7f5f321f715fae9f3b0d6ec92e11d47 /sys/dev
parentb384b103666b328b1445d018911bf0ac9d016351 (diff)
Recognize port status changes.
Hand over low and full speed devices to companion controller.
Diffstat (limited to 'sys/dev')
-rw-r--r--sys/dev/usb/ehci.c240
-rw-r--r--sys/dev/usb/ehcireg.h14
-rw-r--r--sys/dev/usb/ehcivar.h8
3 files changed, 246 insertions, 16 deletions
diff --git a/sys/dev/usb/ehci.c b/sys/dev/usb/ehci.c
index 1f2c324bc74..141e592d1d5 100644
--- a/sys/dev/usb/ehci.c
+++ b/sys/dev/usb/ehci.c
@@ -1,4 +1,4 @@
-/* $NetBSD: ehci.c,v 1.5 2001/11/15 23:25:09 augustss Exp $ */
+/* $NetBSD: ehci.c,v 1.6 2001/11/16 01:57:08 augustss Exp $ */
/*
* Copyright (c) 2001 The NetBSD Foundation, Inc.
@@ -45,7 +45,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.5 2001/11/15 23:25:09 augustss Exp $");
+__KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.6 2001/11/16 01:57:08 augustss Exp $");
#include <sys/param.h>
#include <sys/systm.h>
@@ -71,7 +71,7 @@ __KERNEL_RCSID(0, "$NetBSD: ehci.c,v 1.5 2001/11/15 23:25:09 augustss Exp $");
#ifdef EHCI_DEBUG
#define DPRINTF(x) if (ehcidebug) printf x
#define DPRINTFN(n,x) if (ehcidebug>(n)) printf x
-int ehcidebug = 5;
+int ehcidebug = 0;
#define bitmask_snprintf(q,f,b,l) snprintf((b), (l), "%b", (q), (f))
#else
#define DPRINTF(x)
@@ -135,9 +135,15 @@ Static void ehci_device_clear_toggle(usbd_pipe_handle pipe);
Static void ehci_noop(usbd_pipe_handle pipe);
Static int ehci_str(usb_string_descriptor_t *, int, char *);
+Static void ehci_pcd(ehci_softc_t *, usbd_xfer_handle);
+Static void ehci_pcd_able(ehci_softc_t *, int);
+Static void ehci_pcd_enable(void *);
+Static void ehci_disown(ehci_softc_t *, int, int);
#ifdef EHCI_DEBUG
Static void ehci_dumpregs(ehci_softc_t *);
+Static void ehci_dump(void);
+Static ehci_softc_t *theehci;
#endif
#define EHCI_INTR_ENDPT 1
@@ -214,6 +220,9 @@ ehci_init(ehci_softc_t *sc)
usbd_status err;
DPRINTF(("ehci_init: start\n"));
+#ifdef EHCI_DEBUG
+ theehci = sc;
+#endif
sc->sc_offs = EREAD1(sc, EHCI_CAPLENGTH);
@@ -223,6 +232,7 @@ ehci_init(ehci_softc_t *sc)
sparams = EREAD4(sc, EHCI_HCSPARAMS);
DPRINTF(("ehci_init: sparams=0x%x\n", sparams));
+ sc->sc_npcomp = EHCI_HCS_N_PCC(sparams);
if (EHCI_HCS_N_CC(sparams) != sc->sc_ncomp) {
printf("%s: wrong number of companions (%d != %d)\n",
USBDEVNAME(sc->sc_bus.bdev),
@@ -273,6 +283,8 @@ ehci_init(ehci_softc_t *sc)
return (err);
DPRINTF(("%s: flsize=%d\n", USBDEVNAME(sc->sc_bus.bdev),sc->sc_flsize));
+ usb_callout_init(sc->sc_tmo_pcd);
+
/* Set up the bus struct. */
sc->sc_bus.methods = &ehci_bus_methods;
sc->sc_bus.pipe_size = sizeof(struct ehci_pipe);
@@ -280,13 +292,159 @@ ehci_init(ehci_softc_t *sc)
sc->sc_powerhook = powerhook_establish(ehci_power, sc);
sc->sc_shutdownhook = shutdownhook_establish(ehci_shutdown, sc);
+ sc->sc_eintrs = EHCI_NORMAL_INTRS;
+
+ /* Enable interrupts */
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+
+ /* Turn on controller */
+ EOWRITE4(sc, EHCI_USBCMD,
+ EHCI_CMD_ITC_8 | /* 8 microframes */
+ (EOREAD4(sc, EHCI_USBCMD) & EHCI_CMD_FLS_M) |
+ /* EHCI_CMD_ASE | */
+ /* EHCI_CMD_PSE | */
+ EHCI_CMD_RS);
+
+ /* Take over port ownership */
+ EOWRITE4(sc, EHCI_CONFIGFLAG, EHCI_CONF_CF);
+
return (USBD_NORMAL_COMPLETION);
}
+Static int ehci_intr1(ehci_softc_t *);
+
int
ehci_intr(void *v)
{
- return (0);
+ ehci_softc_t *sc = v;
+
+ /* If we get an interrupt while polling, then just ignore it. */
+ if (sc->sc_bus.use_polling) {
+#ifdef DIAGNOSTIC
+ printf("ehci_intr: ignored interrupt while polling\n");
+#endif
+ return (0);
+ }
+
+ return (ehci_intr1(sc));
+}
+
+Static int
+ehci_intr1(ehci_softc_t *sc)
+{
+ u_int32_t intrs, eintrs;
+
+ DPRINTFN(20,("ehci_intr1: enter\n"));
+
+ /* In case the interrupt occurs before initialization has completed. */
+ if (sc == NULL) {
+#ifdef DIAGNOSTIC
+ printf("ehci_intr: sc == NULL\n");
+#endif
+ return (0);
+ }
+
+ intrs = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS));
+
+ if (!intrs)
+ return (0);
+
+ EOWRITE4(sc, EHCI_USBSTS, intrs); /* Acknowledge */
+ eintrs = intrs & sc->sc_eintrs;
+ DPRINTFN(7, ("ehci_intr: sc=%p intrs=0x%x(0x%x) eintrs=0x%x\n",
+ sc, (u_int)intrs, EOREAD4(sc, EHCI_USBSTS),
+ (u_int)eintrs));
+ if (!eintrs)
+ return (0);
+
+ sc->sc_bus.intr_context++;
+ sc->sc_bus.no_intrs++;
+ if (eintrs & EHCI_STS_INT) {
+ DPRINTF(("ehci_intr1: something is done\n"));
+ eintrs &= ~EHCI_STS_INT;
+ }
+ if (eintrs & EHCI_STS_ERRINT) {
+ DPRINTF(("ehci_intr1: some error\n"));
+ eintrs &= ~EHCI_STS_HSE;
+ }
+ if (eintrs & EHCI_STS_HSE) {
+ printf("%s: unrecoverable error, controller halted\n",
+ USBDEVNAME(sc->sc_bus.bdev));
+ /* XXX what else */
+ }
+ if (eintrs & EHCI_STS_PCD) {
+ ehci_pcd(sc, sc->sc_intrxfer);
+ /*
+ * Disable PCD interrupt for now, because it will be
+ * on until the port has been reset.
+ */
+ ehci_pcd_able(sc, 0);
+ /* Do not allow RHSC interrupts > 1 per second */
+ usb_callout(sc->sc_tmo_pcd, hz, ehci_pcd_enable, sc);
+ eintrs &= ~EHCI_STS_PCD;
+ }
+
+ sc->sc_bus.intr_context--;
+
+ if (eintrs != 0) {
+ /* Block unprocessed interrupts. */
+ sc->sc_eintrs &= ~eintrs;
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+ printf("%s: blocking intrs 0x%x\n",
+ USBDEVNAME(sc->sc_bus.bdev), eintrs);
+ }
+
+ return (1);
+}
+
+void
+ehci_pcd_able(ehci_softc_t *sc, int on)
+{
+ DPRINTFN(4, ("ehci_pcd_able: on=%d\n", on));
+ if (on)
+ sc->sc_eintrs |= EHCI_STS_PCD;
+ else
+ sc->sc_eintrs &= ~EHCI_STS_PCD;
+ EOWRITE4(sc, EHCI_USBINTR, sc->sc_eintrs);
+}
+
+void
+ehci_pcd_enable(void *v_sc)
+{
+ ehci_softc_t *sc = v_sc;
+
+ ehci_pcd_able(sc, 1);
+}
+
+void
+ehci_pcd(ehci_softc_t *sc, usbd_xfer_handle xfer)
+{
+ usbd_pipe_handle pipe;
+ struct ehci_pipe *opipe;
+ u_char *p;
+ int i, m;
+
+ if (xfer == NULL) {
+ /* Just ignore the change. */
+ return;
+ }
+
+ pipe = xfer->pipe;
+ opipe = (struct ehci_pipe *)pipe;
+
+ p = KERNADDR(&xfer->dmabuf);
+ m = min(sc->sc_noport, xfer->length * 8 - 1);
+ memset(p, 0, xfer->length);
+ for (i = 1; i <= m; i++) {
+ /* Pick out CHANGE bits from the status reg. */
+ if (EOREAD4(sc, EHCI_PORTSC(i)) & EHCI_PS_CLEAR)
+ p[i/8] |= 1 << (i%8);
+ }
+ DPRINTF(("ehci_pcd: change=0x%02x\n", *p));
+ xfer->actlen = xfer->length;
+ xfer->status = USBD_NORMAL_COMPLETION;
+
+ usb_transfer_complete(xfer);
}
void
@@ -298,21 +456,19 @@ ehci_softintr(void *v)
void
ehci_poll(struct usbd_bus *bus)
{
-#if 0
ehci_softc_t *sc = (ehci_softc_t *)bus;
#ifdef EHCI_DEBUG
static int last;
int new;
- new = OREAD4(sc, EHCI_INTERRUPT_STATUS);
+ new = EHCI_STS_INTRS(EOREAD4(sc, EHCI_USBSTS));
if (new != last) {
DPRINTFN(10,("ehci_poll: intrs=0x%04x\n", new));
last = new;
}
#endif
- if (OREAD4(sc, EHCI_INTERRUPT_STATUS) & sc->sc_eintrs)
+ if (EOREAD4(sc, EHCI_USBSTS) & sc->sc_eintrs)
ehci_intr1(sc);
-#endif
}
int
@@ -326,6 +482,8 @@ ehci_detach(struct ehci_softc *sc, int flags)
if (rv != 0)
return (rv);
+ usb_uncallout(sc->sc_tmo_pcd, ehci_pcd_enable, sc);
+
if (sc->sc_powerhook != NULL)
powerhook_disestablish(sc->sc_powerhook);
if (sc->sc_shutdownhook != NULL)
@@ -506,7 +664,20 @@ ehci_noop(usbd_pipe_handle pipe)
void
ehci_dumpregs(ehci_softc_t *sc)
{
- // OOO
+ int i;
+ printf("cmd=0x%08x, sts=0x%08x, ien=0x%08x\n",
+ EOREAD4(sc, EHCI_USBCMD),
+ EOREAD4(sc, EHCI_USBSTS),
+ EOREAD4(sc, EHCI_USBINTR));
+ for (i = 1; i <= sc->sc_noport; i++)
+ printf("port %d status=0x%08x\n", i,
+ EOREAD4(sc, EHCI_PORTSC(i)));
+}
+
+void
+ehci_dump()
+{
+ ehci_dumpregs(theehci);
}
#endif
@@ -906,7 +1077,7 @@ ehci_root_ctrl_start(usbd_xfer_handle xfer)
EOWRITE4(sc, port, v | EHCI_PS_OCC);
break;
case UHF_C_PORT_RESET:
- /* how? */
+ sc->sc_isreset = 0;
break;
default:
err = USBD_IOERROR;
@@ -921,7 +1092,7 @@ ehci_root_ctrl_start(usbd_xfer_handle xfer)
case UHF_C_PORT_RESET:
/* Enable RHSC interrupt if condition is cleared. */
if ((OREAD4(sc, port) >> 16) == 0)
- ehci_rhsc_able(sc, 1);
+ ehci_pcd_able(sc, 1);
break;
default:
break;
@@ -980,6 +1151,7 @@ ehci_root_ctrl_start(usbd_xfer_handle xfer)
if (v & EHCI_PS_CSC) i |= UPS_C_CONNECT_STATUS;
if (v & EHCI_PS_PEC) i |= UPS_C_PORT_ENABLED;
if (v & EHCI_PS_OCC) i |= UPS_C_OVERCURRENT_INDICATOR;
+ if (sc->sc_isreset) i |= UPS_C_PORT_RESET;
USETW(ps.wPortChange, i);
l = min(len, sizeof ps);
memcpy(buf, &ps, l);
@@ -1007,14 +1179,26 @@ ehci_root_ctrl_start(usbd_xfer_handle xfer)
case UHF_PORT_RESET:
DPRINTFN(5,("ehci_root_ctrl_transfer: reset port %d\n",
index));
+ if (EHCI_PS_IS_LOWSPEED(v)) {
+ /* Low speed device, give up ownership. */
+ ehci_disown(sc, index, 1);
+ break;
+ }
EOWRITE4(sc, port, v | EHCI_PS_PR);
for (i = 0; i < 10; i++) {
usb_delay_ms(&sc->sc_bus, 10); /* XXX */
- if ((EOREAD4(sc, port) & EHCI_PS_PR) == 0)
+ v = EOREAD4(sc, port);
+ if ((v & EHCI_PS_PR) == 0)
break;
}
- DPRINTFN(8,("ehci port %d reset, status = 0x%04x\n",
- index, EOREAD4(sc, port)));
+ if ((v & EHCI_PS_PE) == 0) {
+ /* Not a high speed device, give up ownership.*/
+ ehci_disown(sc, index, 0);
+ break;
+ }
+ sc->sc_isreset = 1;
+ DPRINTF(("ehci port %d reset, status = 0x%04x\n",
+ index, v));
break;
case UHF_PORT_POWER:
DPRINTFN(2,("ehci_root_ctrl_transfer: set port power "
@@ -1040,6 +1224,34 @@ ehci_root_ctrl_start(usbd_xfer_handle xfer)
return (USBD_IN_PROGRESS);
}
+void
+ehci_disown(ehci_softc_t *sc, int index, int lowspeed)
+{
+ int i, port;
+ u_int32_t v;
+
+ DPRINTF(("ehci_disown: index=%d lowspeed=%d\n", index, lowspeed));
+#ifdef DIAGNOSTIC
+ if (sc->sc_npcomp != 0) {
+ i = (index-1) / sc->sc_npcomp;
+ if (i >= sc->sc_ncomp)
+ printf("%s: strange port\n",
+ USBDEVNAME(sc->sc_bus.bdev));
+ else
+ printf("%s: handing over %s speed device on "
+ "port %d to %s\n",
+ USBDEVNAME(sc->sc_bus.bdev),
+ lowspeed ? "low" : "full",
+ index, USBDEVNAME(sc->sc_comps[i]->bdev));
+ } else {
+ printf("%s: npcomp == 0\n", USBDEVNAME(sc->sc_bus.bdev));
+ }
+#endif
+ port = EHCI_PORTSC(index);
+ v = EOREAD4(sc, port) &~ EHCI_PS_CLEAR;
+ EOWRITE4(sc, port, v | EHCI_PS_PO);
+}
+
/* Abort a root control request. */
Static void
ehci_root_ctrl_abort(usbd_xfer_handle xfer)
diff --git a/sys/dev/usb/ehcireg.h b/sys/dev/usb/ehcireg.h
index 7d638bd3537..f1dd3125bda 100644
--- a/sys/dev/usb/ehcireg.h
+++ b/sys/dev/usb/ehcireg.h
@@ -1,4 +1,4 @@
-/* $NetBSD: ehcireg.h,v 1.5 2001/11/15 23:25:09 augustss Exp $ */
+/* $NetBSD: ehcireg.h,v 1.6 2001/11/16 01:57:08 augustss Exp $ */
/*
* Copyright (c) 2001 The NetBSD Foundation, Inc.
@@ -91,12 +91,20 @@
/* EHCI operational registers. Offset given by EHCI_CAPLENGTH register */
#define EHCI_USBCMD 0x00 /* RO, RW, WO Command register */
#define EHCI_CMD_ITC_M 0x00ff0000 /* RW interrupt threshold ctrl */
+#define EHCI_CMD_ITC_1 0x00010000
+#define EHCI_CMD_ITC_2 0x00020000
+#define EHCI_CMD_ITC_4 0x00040000
+#define EHCI_CMD_ITC_8 0x00080000
+#define EHCI_CMD_ITC_16 0x00100000
+#define EHCI_CMD_ITC_32 0x00200000
+#define EHCI_CMD_ITC_64 0x00400000
#define EHCI_CMD_ASPME 0x00000800 /* RW/RO async park enable */
#define EHCI_CMD_ASPMC 0x00000300 /* RW/RO async park count */
#define EHCI_CMD_LHCR 0x00000080 /* RW light host ctrl reset */
#define EHCI_CMD_IAAD 0x00000040 /* RW intr on async adv door bell */
#define EHCI_CMD_ASE 0x00000020 /* RW async sched enable */
#define EHCI_CMD_PSE 0x00000010 /* RW periodic sched enable */
+#define EHCI_CMD_FLS_M 0x0000000c /* RW/RO frame list size */
#define EHCI_CMD_FLS(x) (((x) >> 2) & 3) /* RW/RO frame list size */
#define EHCI_CMD_HCRESET 0x00000002 /* RW reset */
#define EHCI_CMD_RS 0x00000001 /* RW run/stop */
@@ -112,6 +120,9 @@
#define EHCI_STS_PCD 0x00000004 /* RWC port change detect */
#define EHCI_STS_ERRINT 0x00000002 /* RWC error interrupt */
#define EHCI_STS_INT 0x00000001 /* RWC interrupt */
+#define EHCI_STS_INTRS(x) ((x) & 0x2f)
+
+#define EHCI_NORMAL_INTRS (EHCI_STS_IAA | EHCI_STS_HSE | EHCI_STS_PCD | EHCI_STS_ERRINT | EHCI_STS_INT)
#define EHCI_USBINTR 0x08 /* RW Interrupt register */
#define EHCI_INTR_IAAE 0x00000020 /* interrupt on async advance ena */
@@ -140,6 +151,7 @@
#define EHCI_PS_PO 0x00002000 /* RW port owner */
#define EHCI_PS_PP 0x00001000 /* RW,RO port power */
#define EHCI_PS_LS 0x00000c00 /* RO line status */
+#define EHCI_PS_IS_LOWSPEED(x) (((x) & EHCI_PS_LS) == 0x00000400)
#define EHCI_PS_PR 0x00000100 /* RW port reset */
#define EHCI_PS_SUSP 0x00000080 /* RW suspend */
#define EHCI_PS_FPR 0x00000040 /* RW force port resume */
diff --git a/sys/dev/usb/ehcivar.h b/sys/dev/usb/ehcivar.h
index 29d853fb5a5..f9313f4beba 100644
--- a/sys/dev/usb/ehcivar.h
+++ b/sys/dev/usb/ehcivar.h
@@ -1,4 +1,4 @@
-/* $NetBSD: ehcivar.h,v 1.4 2001/11/15 23:25:09 augustss Exp $ */
+/* $NetBSD: ehcivar.h,v 1.5 2001/11/16 01:57:08 augustss Exp $ */
/*
* Copyright (c) 2001 The NetBSD Foundation, Inc.
@@ -51,6 +51,7 @@ typedef struct ehci_softc {
void *sc_shutdownhook; /* cookie from shutdown hook */
u_int sc_ncomp;
+ u_int sc_npcomp;
struct usbd_bus *sc_comps[EHCI_COMPANION_MAX];
usb_dma_t sc_fldma;
@@ -60,9 +61,14 @@ typedef struct ehci_softc {
u_int8_t sc_addr; /* device address */
u_int8_t sc_conf; /* device configuration */
usbd_xfer_handle sc_intrxfer;
+ char sc_isreset;
+
+ u_int32_t sc_eintrs;
SIMPLEQ_HEAD(, usbd_xfer) sc_free_xfers; /* free xfers */
+ usb_callout_t sc_tmo_pcd;
+
device_ptr_t sc_child; /* /dev/usb# device */
char sc_dying;