summaryrefslogtreecommitdiffstats
path: root/sys/dev/uart/uart_dev_ns8250.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/uart/uart_dev_ns8250.c')
-rw-r--r--sys/dev/uart/uart_dev_ns8250.c76
1 files changed, 60 insertions, 16 deletions
diff --git a/sys/dev/uart/uart_dev_ns8250.c b/sys/dev/uart/uart_dev_ns8250.c
index 99692e6..67149d6 100644
--- a/sys/dev/uart/uart_dev_ns8250.c
+++ b/sys/dev/uart/uart_dev_ns8250.c
@@ -50,6 +50,7 @@ __FBSDID("$FreeBSD$");
#endif
#include <dev/uart/uart_bus.h>
#include <dev/uart/uart_dev_ns8250.h>
+#include <dev/uart/uart_ppstypes.h>
#include <dev/ic/ns16550.h>
@@ -390,11 +391,40 @@ static struct ofw_compat_data compat_data[] = {
UART_FDT_CLASS_AND_DEVICE(compat_data);
#endif
-#define SIGCHG(c, i, s, d) \
- if (c) { \
- i |= (i & s) ? s : s | d; \
- } else { \
- i = (i & s) ? (i & ~s) | d : i; \
+/* Use token-pasting to form SER_ and MSR_ named constants. */
+#define SER(sig) SER_##sig
+#define SERD(sig) SER_D##sig
+#define MSR(sig) MSR_##sig
+#define MSRD(sig) MSR_D##sig
+
+/*
+ * Detect signal changes using software delta detection. The previous state of
+ * the signals is in 'var' the new hardware state is in 'msr', and 'sig' is the
+ * short name (DCD, CTS, etc) of the signal bit being processed; 'var' gets the
+ * new state of both the signal and the delta bits.
+ */
+#define SIGCHGSW(var, msr, sig) \
+ if ((msr) & MSR(sig)) { \
+ if ((var & SER(sig)) == 0) \
+ var |= SERD(sig) | SER(sig); \
+ } else { \
+ if ((var & SER(sig)) != 0) \
+ var = SERD(sig) | (var & ~SER(sig)); \
+ }
+
+/*
+ * Detect signal changes using the hardware msr delta bits. This is currently
+ * used only when PPS timing information is being captured using the "narrow
+ * pulse" option. With a narrow PPS pulse the signal may not still be asserted
+ * by time the interrupt handler is invoked. The hardware will latch the fact
+ * that it changed in the delta bits.
+ */
+#define SIGCHGHW(var, msr, sig) \
+ if ((msr) & MSRD(sig)) { \
+ if (((msr) & MSR(sig)) != 0) \
+ var |= SERD(sig) | SER(sig); \
+ else \
+ var = SERD(sig) | (var & ~SER(sig)); \
}
int
@@ -521,21 +551,37 @@ ns8250_bus_flush(struct uart_softc *sc, int what)
int
ns8250_bus_getsig(struct uart_softc *sc)
{
- uint32_t new, old, sig;
+ uint32_t old, sig;
uint8_t msr;
+ /*
+ * The delta bits are reputed to be broken on some hardware, so use
+ * software delta detection by default. Use the hardware delta bits
+ * when capturing PPS pulses which are too narrow for software detection
+ * to see the edges. Hardware delta for RI doesn't work like the
+ * others, so always use software for it. Other threads may be changing
+ * other (non-MSR) bits in sc_hwsig, so loop until it can succesfully
+ * update without other changes happening. Note that the SIGCHGxx()
+ * macros carefully preserve the delta bits when we have to loop several
+ * times and a signal transitions between iterations.
+ */
do {
old = sc->sc_hwsig;
sig = old;
uart_lock(sc->sc_hwmtx);
msr = uart_getreg(&sc->sc_bas, REG_MSR);
uart_unlock(sc->sc_hwmtx);
- SIGCHG(msr & MSR_DSR, sig, SER_DSR, SER_DDSR);
- SIGCHG(msr & MSR_CTS, sig, SER_CTS, SER_DCTS);
- SIGCHG(msr & MSR_DCD, sig, SER_DCD, SER_DDCD);
- SIGCHG(msr & MSR_RI, sig, SER_RI, SER_DRI);
- new = sig & ~SER_MASK_DELTA;
- } while (!atomic_cmpset_32(&sc->sc_hwsig, old, new));
+ if (sc->sc_pps_mode & UART_PPS_NARROW_PULSE) {
+ SIGCHGHW(sig, msr, DSR);
+ SIGCHGHW(sig, msr, CTS);
+ SIGCHGHW(sig, msr, DCD);
+ } else {
+ SIGCHGSW(sig, msr, DSR);
+ SIGCHGSW(sig, msr, CTS);
+ SIGCHGSW(sig, msr, DCD);
+ }
+ SIGCHGSW(sig, msr, RI);
+ } while (!atomic_cmpset_32(&sc->sc_hwsig, old, sig & ~SER_MASK_DELTA));
return (sig);
}
@@ -889,12 +935,10 @@ ns8250_bus_setsig(struct uart_softc *sc, int sig)
old = sc->sc_hwsig;
new = old;
if (sig & SER_DDTR) {
- SIGCHG(sig & SER_DTR, new, SER_DTR,
- SER_DDTR);
+ new = (new & ~SER_DTR) | (sig & (SER_DTR | SER_DDTR));
}
if (sig & SER_DRTS) {
- SIGCHG(sig & SER_RTS, new, SER_RTS,
- SER_DRTS);
+ new = (new & ~SER_RTS) | (sig & (SER_RTS | SER_DRTS));
}
} while (!atomic_cmpset_32(&sc->sc_hwsig, old, new));
uart_lock(sc->sc_hwmtx);
OpenPOWER on IntegriCloud