Module Name:    src
Committed By:   thorpej
Date:           Sun Dec 22 23:24:56 UTC 2019

Modified Files:
        src/sys/arch/arm/broadcom: bcm2835_bsc.c

Log Message:
Rewrite the bcm2835 i2c driver as an interrupt-driven state machine.  This
improves general system responsiveness when tranferring large amounts of
data on a single-core Pi board.  This also includes:

Cleanup i2c bus acquire / release, centralizing all of the logic into
iic_acquire_bus() / iic_release_bus().  "acquire" and "release" hooks
no longer need to be provided by back-end controller drivers (only if
they need special handling, e.g. powering on the i2c controller).
This results in the removal of a bunch of rendundant code from each
back-end controller driver.

Assert that we are not in hard interrupt context in iic_acquire_bus(),
iic_exec(), and iic_release_bus().


To generate a diff of this commit:
cvs rdiff -u -r1.13 -r1.14 src/sys/arch/arm/broadcom/bcm2835_bsc.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/arm/broadcom/bcm2835_bsc.c
diff -u src/sys/arch/arm/broadcom/bcm2835_bsc.c:1.13 src/sys/arch/arm/broadcom/bcm2835_bsc.c:1.14
--- src/sys/arch/arm/broadcom/bcm2835_bsc.c:1.13	Sun Jul  1 21:23:16 2018
+++ src/sys/arch/arm/broadcom/bcm2835_bsc.c	Sun Dec 22 23:24:56 2019
@@ -1,6 +1,7 @@
-/*	$NetBSD: bcm2835_bsc.c,v 1.13 2018/07/01 21:23:16 jmcneill Exp $	*/
+/*	$NetBSD: bcm2835_bsc.c,v 1.14 2019/12/22 23:24:56 thorpej Exp $	*/
 
 /*
+ * Copyright (c) 2019 Jason R. Thorpe
  * Copyright (c) 2012 Jonathan A. Kollasch
  * All rights reserved.
  *
@@ -27,11 +28,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: bcm2835_bsc.c,v 1.13 2018/07/01 21:23:16 jmcneill Exp $");
-
-#if defined(_KERNEL_OPT)
-#include "opt_kernhist.h"
-#endif
+__KERNEL_RCSID(0, "$NetBSD: bcm2835_bsc.c,v 1.14 2019/12/22 23:24:56 thorpej Exp $");
 
 #include <sys/param.h>
 #include <sys/bus.h>
@@ -39,7 +36,6 @@ __KERNEL_RCSID(0, "$NetBSD: bcm2835_bsc.
 #include <sys/kernhist.h>
 #include <sys/intr.h>
 #include <sys/mutex.h>
-#include <sys/once.h>
 #include <sys/systm.h>
 
 #include <dev/i2c/i2cvar.h>
@@ -49,42 +45,118 @@ __KERNEL_RCSID(0, "$NetBSD: bcm2835_bsc.
 
 #include <dev/fdt/fdtvar.h>
 
-KERNHIST_DEFINE(bsciichist);
+typedef enum {
+	BSC_EXEC_STATE_IDLE		= 0,
+	BSC_EXEC_STATE_SEND_ADDR	= 1,
+	BSC_EXEC_STATE_SEND_CMD		= 2,
+	BSC_EXEC_STATE_SEND_DATA	= 3,
+	BSC_EXEC_STATE_RECV_DATA	= 4,
+	BSC_EXEC_STATE_DONE		= 5,
+	BSC_EXEC_STATE_ERROR		= 6,
+} bsc_exec_state_t;
+
+#define	BSC_EXEC_STATE_SENDING(sc)	\
+	((sc)->sc_exec_state >= BSC_EXEC_STATE_SEND_ADDR && \
+	 (sc)->sc_exec_state <= BSC_EXEC_STATE_SEND_DATA)
+
+#define	BSC_EXEC_STATE_RECEIVING(sc)	\
+	((sc)->sc_exec_state == BSC_EXEC_STATE_RECV_DATA)
 
 struct bsciic_softc {
 	device_t sc_dev;
 	bus_space_tag_t sc_iot;
 	bus_space_handle_t sc_ioh;
 	struct i2c_controller sc_i2c;
-	kmutex_t sc_buslock;
 	void *sc_inth;
 
 	struct clk *sc_clk;
 	u_int sc_frequency;
 	u_int sc_clkrate;
+
+	kmutex_t sc_intr_lock;
+	kcondvar_t sc_intr_wait;
+
+	struct {
+		i2c_op_t op;
+		i2c_addr_t addr;
+		const void *cmdbuf;
+		size_t cmdlen;
+		void *databuf;
+		size_t datalen;
+		int flags;
+	} sc_exec;
+
+	/*
+	 * Everything below here protected by the i2c controller lock
+	 * /and/ sc_intr_lock (if we're using interrupts).
+	 */
+
+	bsc_exec_state_t sc_exec_state;
+
+	uint8_t *sc_buf;
+	size_t sc_bufpos;
+	size_t sc_buflen;
+
+	uint32_t sc_c_bits;
+	bool sc_expecting_interrupt;
+};
+
+static void	bsciic_exec_func_idle(struct bsciic_softc * const);
+static void	bsciic_exec_func_send_addr(struct bsciic_softc * const);
+static void	bsciic_exec_func_send_cmd(struct bsciic_softc * const);
+static void	bsciic_exec_func_send_data(struct bsciic_softc * const);
+static void	bsciic_exec_func_recv_data(struct bsciic_softc * const);
+static void	bsciic_exec_func_done(struct bsciic_softc * const);
+static void	bsciic_exec_func_error(struct bsciic_softc * const);
+
+const struct {
+	void			(*func)(struct bsciic_softc * const);
+	uint32_t		c_bits;
+	uint32_t		s_bits;
+} bsciic_exec_state_data[] = {
+	[BSC_EXEC_STATE_IDLE] = {
+		.func = bsciic_exec_func_idle,
+	},
+	[BSC_EXEC_STATE_SEND_ADDR] = {
+		.func = bsciic_exec_func_send_addr,
+	},
+	[BSC_EXEC_STATE_SEND_CMD] = {
+		.func = bsciic_exec_func_send_cmd,
+		.c_bits = BSC_C_INTT,
+		.s_bits = BSC_S_TXW,
+	},
+	[BSC_EXEC_STATE_SEND_DATA] = {
+		.func = bsciic_exec_func_send_data,
+		.c_bits = BSC_C_INTT,
+		.s_bits = BSC_S_TXW,
+	},
+	[BSC_EXEC_STATE_RECV_DATA] = {
+		.func = bsciic_exec_func_recv_data,
+		.c_bits = BSC_C_READ | BSC_C_INTR,
+		.s_bits = BSC_S_RXR,
+	},
+	[BSC_EXEC_STATE_DONE] = {
+		.func = bsciic_exec_func_done,
+	},
+	[BSC_EXEC_STATE_ERROR] = {
+		.func = bsciic_exec_func_error,
+	},
 };
 
 static int bsciic_match(device_t, cfdata_t, void *);
 static void bsciic_attach(device_t, device_t, void *);
 
-void bsciic_dump_regs(struct bsciic_softc * const);
-
 static int  bsciic_acquire_bus(void *, int);
 static void bsciic_release_bus(void *, int);
 static int  bsciic_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t,
-    void *, size_t, int);
-
-CFATTACH_DECL_NEW(bsciic, sizeof(struct bsciic_softc),
-    bsciic_match, bsciic_attach, NULL, NULL);
+			void *, size_t, int);
 
-static int
-bsciic_init(void)
-{
+static int bsciic_intr(void *);
 
-	KERNHIST_INIT(bsciichist, 512);
+int	bsciic_debug = 0;
 
-	return 0;
-}
+CFATTACH_DECL_NEW(bsciic, sizeof(struct bsciic_softc),
+    bsciic_match, bsciic_attach, NULL, NULL);
 
 static int
 bsciic_match(device_t parent, cfdata_t match, void *aux)
@@ -104,9 +176,6 @@ bsciic_attach(device_t parent, device_t 
 	prop_dictionary_t prop = device_properties(self);
 	bool disable = false;
 
-	static ONCE_DECL(control);
-	RUN_ONCE(&control, bsciic_init);
-
 	bus_addr_t addr;
 	bus_size_t size;
 
@@ -146,24 +215,41 @@ bsciic_attach(device_t parent, device_t 
 	}
 
 	if (bus_space_map(sc->sc_iot, addr, size, 0, &sc->sc_ioh)) {
-		aprint_error_dev(sc->sc_dev, "unable to map device\n");
+		aprint_error(": unable to map device\n");
 		return;
 	}
 
 	aprint_naive("\n");
 	aprint_normal(": Broadcom Serial Controller\n");
 
-	mutex_init(&sc->sc_buslock, MUTEX_DEFAULT, IPL_NONE);
-
 	/* clear FIFO, disable controller */
 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_C, BSC_C_CLEAR_CLEAR);
 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_S, BSC_S_CLKT |
 	    BSC_S_ERR | BSC_S_DONE);
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_DLEN, 0);
 
 	u_int divider = howmany(sc->sc_frequency, sc->sc_clkrate);
 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_DIV,
 	   __SHIFTIN(divider, BSC_DIV_CDIV));
 
+	mutex_init(&sc->sc_intr_lock, MUTEX_DEFAULT, IPL_VM);
+	cv_init(&sc->sc_intr_wait, device_xname(self));
+
+	char intrstr[128];
+	if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
+		aprint_error_dev(sc->sc_dev, "failed to decode interrupt\n");
+		return;
+	}
+	sc->sc_inth = fdtbus_intr_establish(phandle, 0, IPL_VM,
+	    FDT_INTR_MPSAFE, bsciic_intr, sc);
+	if (sc->sc_inth == NULL) {
+		aprint_error_dev(sc->sc_dev,
+		    "failed to establish interrupt %s\n", intrstr);
+		return;
+	}
+	aprint_normal_dev(sc->sc_dev, "interrupting on %s\n", intrstr);
+
+	iic_tag_init(&sc->sc_i2c);
 	sc->sc_i2c.ic_cookie = sc;
 	sc->sc_i2c.ic_acquire_bus = bsciic_acquire_bus;
 	sc->sc_i2c.ic_release_bus = bsciic_release_bus;
@@ -172,28 +258,12 @@ bsciic_attach(device_t parent, device_t 
 	fdtbus_attach_i2cbus(self, phandle, &sc->sc_i2c, iicbus_print);
 }
 
-void
-bsciic_dump_regs(struct bsciic_softc * const sc)
-{
-	KERNHIST_FUNC(__func__);
-	KERNHIST_CALLED(bsciichist);
-
-	KERNHIST_LOG(bsciichist, "C %08jx S %08jx D %08jx A %08jx",
-	    bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_C),
-	    bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S),
-	    bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_DLEN),
-	    bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_A)
-	    );
-}
-
 static int
 bsciic_acquire_bus(void *v, int flags)
 {
 	struct bsciic_softc * const sc = v;
 	uint32_t s __diagused;
 
-	mutex_enter(&sc->sc_buslock);
-
 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_S, BSC_S_CLKT |
 	    BSC_S_ERR | BSC_S_DONE);
 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_C, BSC_C_I2CEN |
@@ -210,203 +280,365 @@ bsciic_release_bus(void *v, int flags)
 	struct bsciic_softc * const sc = v;
 
 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_C, BSC_C_CLEAR_CLEAR);
-
-	mutex_exit(&sc->sc_buslock);
 }
 
-static int
-bsciic_exec(void *v, i2c_op_t op, i2c_addr_t addr, const void *cmdbuf,
-    size_t cmdlen, void *databuf, size_t datalen, int flags)
+static void
+bsciic_exec_lock(struct bsciic_softc * const sc)
 {
-	KERNHIST_FUNC(__func__); KERNHIST_CALLED(bsciichist);
-	struct bsciic_softc * const sc = v;
-	uint32_t c, s, dlen, a;
-	uint32_t j;
-	uint8_t *buf;
-	size_t len;
-	size_t pos;
-	int error = 0;
-	int whichbuf = 0;
-	const bool isread = I2C_OP_READ_P(op);
+	if ((sc->sc_exec.flags & I2C_F_POLL) == 0) {
+		mutex_enter(&sc->sc_intr_lock);
+	}
+}
 
-	/*
-	 * XXX We don't do 10-bit addressing correctly yet.
-	 */
-	if (addr > 0x7f)
-		return (ENOTSUP);
+static void
+bsciic_exec_unlock(struct bsciic_softc * const sc)
+{
+	if ((sc->sc_exec.flags & I2C_F_POLL) == 0) {
+		mutex_exit(&sc->sc_intr_lock);
+	}
+}
 
-	flags |= I2C_F_POLL;
+static void
+bsciic_txfill(struct bsciic_softc * const sc)
+{
+	uint32_t s;
 
-#if 0
-	device_printf(sc->sc_dev, "exec: op %d, addr 0x%x, cmdbuf %p, "
-	    "cmdlen %zu, databuf %p, datalen %zu, flags 0x%x\n",
-	    op, addr, cmdbuf, cmdlen, databuf, datalen, flags);
-#endif
-
-	a = __SHIFTIN(addr, BSC_A_ADDR);
-	c = BSC_C_I2CEN | BSC_C_CLEAR_CLEAR;
-#if notyet
-	c |= BSC_C_INTR | BSC_C_INTT | BSC_C_INTD;
-#endif
-
-	if (isread && cmdlen == 0)
-		goto only_read;
-
-	buf = __UNCONST(cmdbuf);
-	len = cmdlen;
-
-	if (isread)
-		dlen = cmdlen;
-	else
-		dlen = cmdlen + datalen;
-	dlen = __SHIFTIN(dlen, BSC_DLEN_DLEN);
+	while (sc->sc_bufpos != sc->sc_buflen) {
+		s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
+		if ((s & BSC_S_TXD) == 0)
+			break;
+		bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_FIFO,
+		    sc->sc_buf[sc->sc_bufpos++]);
+	}
+}
 
-	s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
-	if ((s & BSC_S_TA) != 0)
-		bsciic_dump_regs(sc);
-	KASSERT((s & BSC_S_TA) == 0);
-	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_DLEN, dlen);
-	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_A, a);
-	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_C, c | BSC_C_ST);
+static void
+bsciic_rxdrain(struct bsciic_softc * const sc)
+{
+	uint32_t s;
 
-	do {
+	while (sc->sc_bufpos != sc->sc_buflen) {
 		s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
-	} while ((s & BSC_S_TA) == 0);
+		if ((s & BSC_S_RXD) == 0)
+			break;
+		sc->sc_buf[sc->sc_bufpos++] =
+		    (uint8_t)bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_FIFO);
+	}
+}
 
-flood_again:
-	KERNHIST_LOG(bsciichist, "flood top %#jx %ju",
-	    (uintptr_t)buf, len, 0, 0);
-	j = 10000000;
-	for (pos = 0; pos < len; ) {
-		if (--j == 0)
-			return -1;
-		s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
-		KERNHIST_LOG(bsciichist, "w s %08jx", s, 0, 0, 0);
-		if ((s & BSC_S_CLKT) != 0) {
-			error = EIO;
-			goto done;
+static bsc_exec_state_t
+bsciic_next_state(struct bsciic_softc * const sc)
+{
+	switch (sc->sc_exec_state) {
+	case BSC_EXEC_STATE_IDLE:
+		if (sc->sc_exec.addr > 0x7f) {
+			return BSC_EXEC_STATE_SEND_ADDR;
 		}
-		if ((s & BSC_S_ERR) != 0) {
-			error = EIO;
-			goto done;
+		/* FALLTHROUGH */
+
+	case BSC_EXEC_STATE_SEND_ADDR:
+		if (sc->sc_exec.cmdlen) {
+			return BSC_EXEC_STATE_SEND_CMD;
 		}
-		if ((s & BSC_S_DONE) != 0)
-			break;
-		if ((s & BSC_S_TXD) == 0)
-			continue;
-		bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_FIFO, buf[pos]);
-		KERNHIST_LOG(bsciichist, "w %#jx %#jx %02jx",
-		    (uintptr_t)buf, (uintptr_t)&buf[pos],
-		    buf[pos], 0);
-		pos++;
-	}
-	KERNHIST_LOG(bsciichist, "flood bot %#jx %ju",
-	    (uintptr_t)buf, len, 0, 0);
-
-	if (whichbuf == 0 && !isread) {
-		KASSERT(buf == cmdbuf);
-		whichbuf++;
-		buf = databuf;
-		len = datalen;
-		if (len)
-			goto flood_again;
+		/* FALLTHROUGH */
+
+	case BSC_EXEC_STATE_SEND_CMD:
+		if (sc->sc_exec.datalen == 0) {
+			return BSC_EXEC_STATE_DONE;
+		}
+
+		if (I2C_OP_READ_P(sc->sc_exec.op)) {
+			return BSC_EXEC_STATE_RECV_DATA;
+		}
+
+		return BSC_EXEC_STATE_SEND_DATA;
+
+	case BSC_EXEC_STATE_SEND_DATA:
+	case BSC_EXEC_STATE_RECV_DATA:
+		return BSC_EXEC_STATE_DONE;
+	
+	case BSC_EXEC_STATE_DONE:
+	case BSC_EXEC_STATE_ERROR:
+		return sc->sc_exec_state;
 	}
 
-	do {
-		s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
-	} while ((s & BSC_S_TA) != 0);
+	panic("bsciic_next_state: invalid state: %d", sc->sc_exec_state);
+}
+
+#define	BSC_EXEC_PHASE_COMPLETE(sc)				\
+	((sc)->sc_exec_state == BSC_EXEC_STATE_ERROR ||		\
+	 (sc)->sc_bufpos == (sc)->sc_buflen)
+
+static void
+bsciic_signal(struct bsciic_softc * const sc)
+{
+	if ((sc->sc_exec.flags & I2C_F_POLL) == 0) {
+		cv_signal(&sc->sc_intr_wait);
+	}
+}
+
+static void
+bsciic_phase_done(struct bsciic_softc * const sc)
+{
+	sc->sc_exec_state = bsciic_next_state(sc);
+	(*bsciic_exec_state_data[sc->sc_exec_state].func)(sc);
+}
+
+static void
+bsciic_abort(struct bsciic_softc * const sc)
+{
+	sc->sc_exec_state = BSC_EXEC_STATE_ERROR;
+	bsciic_phase_done(sc);
+}
+
+static int
+bsciic_intr(void *v)
+{
+	struct bsciic_softc * const sc = v;
+	uint32_t s;
+
+	bsciic_exec_lock(sc);
+
+	if ((sc->sc_exec.flags & I2C_F_POLL) == 0 &&
+	    sc->sc_expecting_interrupt == false) {
+		bsciic_exec_unlock(sc);
+		return 0;
+	}
 
 	s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
-	s &= BSC_S_CLKT|BSC_S_ERR|BSC_S_DONE;
-	KASSERT((s & BSC_S_DONE) != 0);
-	if (s != 0)
-		bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_S, s);
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_S,
+	    BSC_S_CLKT | BSC_S_ERR | BSC_S_DONE);
 
-	if (error == 0 && (s & (BSC_S_CLKT|BSC_S_ERR)) != 0)
-		error = EIO;
+	if (s & (BSC_S_CLKT | BSC_S_ERR)) {
+		/*
+		 * ERR might be a normal "probing for device" sort
+		 * of thing, so don't complain about that one.
+		 * Do complain about CLKT, though.
+		 */
+		if ((s & BSC_S_CLKT) ||
+		    (bsciic_debug && (s & BSC_S_ERR))) {
+			device_printf(sc->sc_dev,
+			    "error s=0x%08x, aborting transfer\n", s);
+		}
+		bsciic_abort(sc);
+		goto out;
+	}
 
-	if (!isread)
-		goto done;
+	if (BSC_EXEC_STATE_SENDING(sc)) {
+		/*
+		 * When transmitting, we need to wait for one final
+		 * interrupt after pushing out the last of our data.
+		 * Catch that case here and go to the next state.
+		 */
+		if (BSC_EXEC_PHASE_COMPLETE(sc)) {
+			bsciic_phase_done(sc);
+		} else {
+			bsciic_txfill(sc);
+		}
+	} else if (BSC_EXEC_STATE_RECEIVING(sc)) {
+		bsciic_rxdrain(sc);
+		/*
+		 * If we've received all of the data, go to the next
+		 * state now; we might not get another interrupt.
+		 */
+		if (BSC_EXEC_PHASE_COMPLETE(sc)) {
+			bsciic_phase_done(sc);
+		}
+	} else {
+		device_printf(sc->sc_dev,
+		    "unexpected interrupt: state=%d s=0x%08x\n",
+		    sc->sc_exec_state, s);
+		bsciic_abort(sc);
+	}
 
-only_read:
-	c |= BSC_C_READ;
+ out:
+	bsciic_exec_unlock(sc);
+	return (1);
+}
 
-	buf = databuf;
-	len = datalen;
-	dlen = datalen;
-
-	dlen = __SHIFTIN(dlen, BSC_DLEN_DLEN);
-#if 0
-	KASSERT(dlen >= 1);
-#endif
-	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_DLEN, dlen);
-	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_A, a);
-	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_C, c | BSC_C_ST);
+static void
+bsciic_wait(struct bsciic_softc * const sc, const uint32_t events)
+{
+	if ((sc->sc_exec.flags & I2C_F_POLL) == 0) {
+		return;
+	}
 
-	do {
-		s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
-	} while ((s & BSC_S_TA) == 0);
+	const uint32_t s_bits =
+	    events | BSC_S_CLKT | BSC_S_ERR | BSC_S_DONE;
+	uint32_t s;
+
+	/* sc_intr_lock is not held in this case. */
 
-	KERNHIST_LOG(bsciichist, "drain top %#jx %ju",
-	    (uintptr_t)buf, len, 0, 0);
-	j = 10000000;
-	for (pos = 0; pos < len; ) {
-		if (--j == 0)
-			return -1;
+	for (;;) {
 		s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
-		KERNHIST_LOG(bsciichist, "r s %08jx", s, 0, 0, 0);
-		if ((s & BSC_S_CLKT) != 0) {
-			error = EIO;
-			goto done;
+		if (s & s_bits) {
+			(void) bsciic_intr(sc);
 		}
-		if ((s & BSC_S_ERR) != 0) {
-			error = EIO;
-			goto done;
+		if (BSC_EXEC_PHASE_COMPLETE(sc)) {
+			bsciic_phase_done(sc);
 		}
-		if ((s & BSC_S_DONE) != 0)
-			break;
-		if ((s & BSC_S_RXD) == 0)
-			continue;
-		j = 10000000;
-		buf[pos] = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_FIFO);
-		KERNHIST_LOG(bsciichist, "r %#jx %#jx %02jx",
-		    (uintptr_t)buf, (uintptr_t)&buf[pos],
-		    buf[pos], 0);
-		pos++;
+		if (sc->sc_exec_state >= BSC_EXEC_STATE_DONE) {
+			return;
+		}
+		delay(1);
 	}
-	KERNHIST_LOG(bsciichist, "drain bot %#jx %ju", (uintptr_t)buf, len,
-	    0, 0);
+}
 
-	do {
-		s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
-	} while ((s & BSC_S_TA) != 0);
+static void
+bsciic_start(struct bsciic_softc * const sc)
+{
 
-	s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
-	s &= BSC_S_CLKT|BSC_S_ERR|BSC_S_DONE;
-	KASSERT((s & BSC_S_DONE) != 0);
-	if (s != 0)
-		bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_S, s);
+	sc->sc_c_bits = BSC_C_I2CEN | BSC_C_INTD |
+	    bsciic_exec_state_data[sc->sc_exec_state].c_bits;
 
-done:
-	bsciic_dump_regs(sc);
+	/* Clear the interrupt-enable bits if we're polling. */
+	if (sc->sc_exec.flags & I2C_F_POLL) {
+		sc->sc_c_bits &= ~(BSC_C_INTD | BSC_C_INTT | BSC_C_INTR);
+	}
 
-	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_DLEN, 0);
+	sc->sc_expecting_interrupt =
+	    (sc->sc_c_bits & (BSC_C_INTD | BSC_C_INTT | BSC_C_INTR)) ? true
+								     : false;
+
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_C,
+	    sc->sc_c_bits | BSC_C_ST);
+	bsciic_wait(sc, bsciic_exec_state_data[sc->sc_exec_state].s_bits);
+}
 
-	do {
-		s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
-	} while ((s & BSC_S_TA) != 0);
+static void
+bsciic_exec_func_idle(struct bsciic_softc * const sc)
+{
+	/* We kick off a transfer by setting the slave address register. */
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_A, sc->sc_exec.addr);
 
-	bsciic_dump_regs(sc);
+	/* Immediately transition to the next state. */
+	bsciic_phase_done(sc);
+}
 
-	s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
-	s &= BSC_S_CLKT|BSC_S_ERR|BSC_S_DONE;
-	if (s != 0)
-		bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_S, s);
+static void
+bsciic_exec_func_send_addr(struct bsciic_softc * const sc)
+{
+	/* XXX For 10-bit addressing; not implemented yet. */
+	panic("bsciic_exec_func_send_addr is not supposed to be called");
+}
+
+static void
+bsciic_exec_func_send_cmd(struct bsciic_softc * const sc)
+{
+	sc->sc_buf = __UNCONST(sc->sc_exec.cmdbuf);
+	sc->sc_bufpos = 0;
+	sc->sc_buflen = sc->sc_exec.cmdlen;
+
+	uint32_t dlen = sc->sc_exec.cmdlen;
+	if (! I2C_OP_READ_P(sc->sc_exec.op)) {
+		dlen += sc->sc_exec.datalen;
+	}
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_DLEN, dlen);
+
+	bsciic_start(sc);
+}
+
+static void
+bsciic_exec_func_send_data(struct bsciic_softc * const sc)
+{
+	sc->sc_buf = sc->sc_exec.databuf;
+	sc->sc_bufpos = 0;
+	sc->sc_buflen = sc->sc_exec.datalen;
+
+	if (sc->sc_exec.cmdlen) {
+		/*
+		 * Output has already been started in this case; we just
+		 * needed to switch buffers.
+		 */
+		bsciic_wait(sc, BSC_S_TXW);
+	} else {
+		uint32_t dlen = sc->sc_exec.datalen;
+		bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_DLEN, dlen);
+		bsciic_start(sc);
+	}
+}
+
+static void
+bsciic_exec_func_recv_data(struct bsciic_softc * const sc)
+{
+	sc->sc_buf = sc->sc_exec.databuf;
+	sc->sc_bufpos = 0;
+	sc->sc_buflen = sc->sc_exec.datalen;
+
+	uint32_t dlen = sc->sc_exec.datalen;
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_DLEN, dlen);
+
+	bsciic_start(sc);
+}
+
+static void
+bsciic_exec_func_done(struct bsciic_softc * const sc)
+{
+	/* We're done!  Disable interrupts. */
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_C, BSC_C_I2CEN);
+	sc->sc_expecting_interrupt = false;
+	bsciic_signal(sc);
+}
+
+static void
+bsciic_exec_func_error(struct bsciic_softc * const sc)
+{
+	/* Clear the FIFO and disable interrupts. */
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_C,
+	    BSC_C_I2CEN | BSC_C_CLEAR_CLEAR);
+	sc->sc_expecting_interrupt = false;
+	bsciic_signal(sc);
+}
 
-	bsciic_dump_regs(sc);
+static int
+bsciic_exec(void *v, i2c_op_t op, i2c_addr_t addr, const void *cmdbuf,
+    size_t cmdlen, void *databuf, size_t datalen, int flags)
+{
+	struct bsciic_softc * const sc = v;
 
-	if (error == 0 && (s & (BSC_S_CLKT|BSC_S_ERR)) != 0)
+	/* XXX We don't do 10-bit addressing correctly yet. */
+	if (addr > 0x7f)
+		return (ENOTSUP);
+
+	/*
+	 * The I2C middle layer has ensured that the client device has
+	 * exclusive access to the controller.  Copy the parameters
+	 * and start the state machine that runs through the necessary
+	 * phases of the request.
+	 */
+	KASSERT(sc->sc_exec_state == BSC_EXEC_STATE_IDLE);
+	sc->sc_exec.op = op;
+	sc->sc_exec.addr = addr;
+	sc->sc_exec.cmdbuf = cmdbuf;
+	sc->sc_exec.cmdlen = cmdlen;
+	sc->sc_exec.databuf = databuf;
+	sc->sc_exec.datalen = datalen;
+	sc->sc_exec.flags = flags;
+
+	bsciic_exec_lock(sc);
+	(*bsciic_exec_state_data[sc->sc_exec_state].func)(sc);
+	while (sc->sc_exec_state < BSC_EXEC_STATE_DONE) {
+		KASSERT((flags & I2C_F_POLL) == 0);
+		cv_wait(&sc->sc_intr_wait, &sc->sc_intr_lock);
+	}
+	int error = sc->sc_exec_state == BSC_EXEC_STATE_ERROR ? EIO : 0;
+	uint32_t s;
+	do {
+		s = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BSC_S);
+	} while ((s & BSC_S_TA) != 0);
+	if (s & (BSC_S_CLKT | BSC_S_ERR)) {
 		error = EIO;
+	}
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_C,
+	    BSC_C_I2CEN | BSC_C_CLEAR_CLEAR);
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_S,
+	    BSC_S_CLKT | BSC_S_ERR | BSC_S_DONE);
+	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BSC_DLEN, 0);
+	bsciic_exec_unlock(sc);
+
+	sc->sc_exec.flags = 0;
+	sc->sc_exec_state = BSC_EXEC_STATE_IDLE;
+	memset(&sc->sc_exec, 0, sizeof(sc->sc_exec));
 
 	return error;
 }

Reply via email to