summaryrefslogtreecommitdiff
path: root/sys/arch/octeon
diff options
context:
space:
mode:
authorVisa Hankala <visa@cvs.openbsd.org>2019-04-23 13:53:48 +0000
committerVisa Hankala <visa@cvs.openbsd.org>2019-04-23 13:53:48 +0000
commit8490cf994127139ef0048da83116139b795b5e4d (patch)
tree4ab0f3e704a2b4450e2019b3e750602e6fb0163c /sys/arch/octeon
parentdb15e483f2f5e75ad8bbfdaf5ee6bff366cddd10 (diff)
Add a driver for OCTEON two-wire serial interface.
Not enabled yet because of a conflict with octrtc(4).
Diffstat (limited to 'sys/arch/octeon')
-rw-r--r--sys/arch/octeon/conf/files.octeon6
-rw-r--r--sys/arch/octeon/dev/octiic.c438
2 files changed, 443 insertions, 1 deletions
diff --git a/sys/arch/octeon/conf/files.octeon b/sys/arch/octeon/conf/files.octeon
index 9c57abd094a..90cf58cedb3 100644
--- a/sys/arch/octeon/conf/files.octeon
+++ b/sys/arch/octeon/conf/files.octeon
@@ -1,4 +1,4 @@
-# $OpenBSD: files.octeon,v 1.49 2019/01/12 16:59:38 visa Exp $
+# $OpenBSD: files.octeon,v 1.50 2019/04/23 13:53:46 visa Exp $
# Standard stanzas config(8) can't run without
maxpartitions 16
@@ -149,6 +149,10 @@ device octgpio
attach octgpio at fdt
file arch/octeon/dev/octgpio.c octgpio
+device octiic: i2cbus
+attach octiic at fdt
+file arch/octeon/dev/octiic.c octiic
+
device octmmc: sdmmcbus
attach octmmc at fdt
file arch/octeon/dev/octmmc.c octmmc
diff --git a/sys/arch/octeon/dev/octiic.c b/sys/arch/octeon/dev/octiic.c
new file mode 100644
index 00000000000..5b942e7a836
--- /dev/null
+++ b/sys/arch/octeon/dev/octiic.c
@@ -0,0 +1,438 @@
+/* $OpenBSD: octiic.c,v 1.1 2019/04/23 13:53:47 visa Exp $ */
+
+/*
+ * Copyright (c) 2019 Visa Hankala
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Driver for OCTEON two-wire serial interface core.
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/stdint.h>
+
+#include <dev/i2c/i2cvar.h>
+#include <dev/ofw/fdt.h>
+#include <dev/ofw/openfirm.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+#include <machine/octeonvar.h>
+
+struct octiic_softc {
+ struct device sc_dev;
+ int sc_node;
+ bus_space_tag_t sc_iot;
+ bus_space_handle_t sc_ioh;
+
+ struct i2c_controller sc_i2c_tag;
+ struct rwlock sc_i2c_lock;
+
+ int sc_start_sent;
+};
+
+int octiic_match(struct device *, void *, void *);
+void octiic_attach(struct device *, struct device *, void *);
+
+int octiic_i2c_acquire_bus(void *, int);
+void octiic_i2c_release_bus(void *, int);
+int octiic_i2c_send_start(void *, int);
+int octiic_i2c_send_stop(void *, int);
+int octiic_i2c_initiate_xfer(void *, i2c_addr_t, int);
+int octiic_i2c_read_byte(void *, uint8_t *, int);
+int octiic_i2c_write_byte(void *, uint8_t, int);
+void octiic_i2c_scan(struct device *, struct i2cbus_attach_args *, void *);
+
+int octiic_reg_read(struct octiic_softc *, uint8_t, uint8_t *);
+int octiic_reg_write(struct octiic_softc *, uint8_t, uint8_t);
+int octiic_set_clock(struct octiic_softc *, uint32_t);
+int octiic_wait(struct octiic_softc *, uint8_t, int);
+
+const struct cfattach octiic_ca = {
+ sizeof(struct octiic_softc), octiic_match, octiic_attach
+};
+
+struct cfdriver octiic_cd = {
+ NULL, "octiic", DV_DULL
+};
+
+#define TWSI_RD_8(sc, reg) \
+ bus_space_read_8((sc)->sc_iot, (sc)->sc_ioh, (reg))
+#define TWSI_WR_8(sc, reg, val) \
+ bus_space_write_8((sc)->sc_iot, (sc)->sc_ioh, (reg), (val))
+
+#define TWSI_SW_TWSI 0x00
+#define TWSI_SW_TWSI_V 0x8000000000000000ull
+#define TWSI_SW_TWSI_SLONLY 0x4000000000000000ull
+#define TWSI_SW_TWSI_EIA 0x2000000000000000ull
+#define TWSI_SW_TWSI_OP_M 0x1e00000000000000ull
+#define TWSI_SW_TWSI_OP_S 57
+#define TWSI_SW_TWSI_R 0x0100000000000000ull
+#define TWSI_SW_TWSI_SOVR 0x0080000000000000ull
+#define TWSI_SW_TWSI_SIZE_M 0x0070000000000000ull
+#define TWSI_SW_TWSI_SIZE_S 52
+#define TWSI_SW_TWSI_SCR_M 0x000c000000000000ull
+#define TWSI_SW_TWSI_SCR_S 50
+#define TWSI_SW_TWSI_A_M 0x0003ff0000000000ull
+#define TWSI_SW_TWSI_A_S 40
+#define TWSI_SW_TWSI_IA_M 0x000000f800000000ull
+#define TWSI_SW_TWSI_IA_S 35
+#define TWSI_SW_TWSI_EOP_IA_M 0x0000000700000000ull
+#define TWSI_SW_TWSI_EOP_IA_S 32
+#define TWSI_SW_TWSI_D_M 0x00000000ffffffffull
+
+/* Opcodes for field TWSI_SW_TWSI_OP */
+#define TWSI_OP_CLK 0x04
+#define TWSI_OP_EOP 0x06
+
+/* Addresses for field TWSI_SW_TWSI_IA */
+#define TWSI_IA_DATA 0x01
+#define TWSI_IA_CTL 0x02
+#define TWSI_IA_CLKCTL 0x03 /* write only */
+#define TWSI_IA_STAT 0x03 /* read only */
+#define TWSI_IA_RST 0x07
+
+#define TWSI_INT 0x10
+
+/* Control register bits */
+#define TWSI_CTL_CE 0x80
+#define TWSI_CTL_ENAB 0x40
+#define TWSI_CTL_STA 0x20
+#define TWSI_CTL_STP 0x10
+#define TWSI_CTL_IFLG 0x08
+#define TWSI_CTL_AAK 0x04
+
+/* Core states */
+#define TWSI_STAT_ERROR 0x00
+#define TWSI_STAT_START 0x08
+#define TWSI_STAT_RSTART 0x10
+#define TWSI_STAT_AWT_ACK 0x18
+#define TWSI_STAT_MBT_ACK 0x28
+#define TWSI_STAT_ART_ACK 0x40
+#define TWSI_STAT_MBR_ACK 0x50
+#define TWSI_STAT_MBR_NAK 0x58
+#define TWSI_STAT_IDLE 0xf8
+
+int
+octiic_match(struct device *parent, void *match, void *aux)
+{
+ struct fdt_attach_args *fa = aux;
+
+ return OF_is_compatible(fa->fa_node, "cavium,octeon-3860-twsi") ||
+ OF_is_compatible(fa->fa_node, "cavium,octeon-7890-twsi");
+}
+
+void
+octiic_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct i2cbus_attach_args iba;
+ struct fdt_attach_args *faa = aux;
+ struct octiic_softc *sc = (struct octiic_softc *)self;
+ uint32_t freq;
+
+ sc->sc_node = faa->fa_node;
+ sc->sc_iot = faa->fa_iot;
+
+ if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr, faa->fa_reg[0].size,
+ 0, &sc->sc_ioh)) {
+ printf(": failed to map registers\n");
+ return;
+ }
+
+ freq = OF_getpropint(faa->fa_node, "clock-frequency", 100000);
+ if (octiic_set_clock(sc, freq) != 0) {
+ printf(": clock setup failed\n");
+ return;
+ }
+
+ /* Reset the controller. */
+ if (octiic_reg_write(sc, TWSI_IA_RST, 0) != 0) {
+ printf(": register write timeout\n");
+ return;
+ }
+
+ delay(1000);
+
+ if (octiic_wait(sc, TWSI_STAT_IDLE, I2C_F_POLL) != 0) {
+ printf(": reset failed\n");
+ return;
+ }
+
+ printf("\n");
+
+ rw_init(&sc->sc_i2c_lock, "iiclk");
+ sc->sc_i2c_tag.ic_cookie = sc;
+ sc->sc_i2c_tag.ic_acquire_bus = octiic_i2c_acquire_bus;
+ sc->sc_i2c_tag.ic_release_bus = octiic_i2c_release_bus;
+ sc->sc_i2c_tag.ic_send_start = octiic_i2c_send_start;
+ sc->sc_i2c_tag.ic_send_stop = octiic_i2c_send_stop;
+ sc->sc_i2c_tag.ic_initiate_xfer = octiic_i2c_initiate_xfer;
+ sc->sc_i2c_tag.ic_read_byte = octiic_i2c_read_byte;
+ sc->sc_i2c_tag.ic_write_byte = octiic_i2c_write_byte;
+
+ memset(&iba, 0, sizeof(iba));
+ iba.iba_name = "iic";
+ iba.iba_tag = &sc->sc_i2c_tag;
+ config_found(self, &iba, iicbus_print);
+}
+
+int
+octiic_i2c_acquire_bus(void *arg, int flags)
+{
+ struct octiic_softc *sc = arg;
+
+ if (cold || (flags & I2C_F_POLL))
+ return 0;
+
+ return rw_enter(&sc->sc_i2c_lock, RW_WRITE | RW_INTR);
+}
+
+void
+octiic_i2c_release_bus(void *arg, int flags)
+{
+ struct octiic_softc *sc = arg;
+
+ if (cold || (flags & I2C_F_POLL))
+ return;
+
+ rw_exit(&sc->sc_i2c_lock);
+}
+
+int
+octiic_i2c_send_start(void *cookie, int flags)
+{
+ struct octiic_softc *sc = cookie;
+ int error;
+ uint8_t nstate;
+
+ error = octiic_reg_write(sc, TWSI_IA_CTL, TWSI_CTL_ENAB | TWSI_CTL_STA);
+ if (error != 0)
+ return error;
+
+ delay(10);
+
+ if (sc->sc_start_sent)
+ nstate = TWSI_STAT_RSTART;
+ else
+ nstate = TWSI_STAT_START;
+ error = octiic_wait(sc, nstate, flags);
+ if (error != 0)
+ return error;
+
+ sc->sc_start_sent = 1;
+
+ return 0;
+}
+
+int
+octiic_i2c_send_stop(void *cookie, int flags)
+{
+ struct octiic_softc *sc = cookie;
+
+ sc->sc_start_sent = 0;
+
+ return octiic_reg_write(sc, TWSI_IA_CTL, TWSI_CTL_ENAB | TWSI_CTL_STP);
+}
+
+int
+octiic_i2c_initiate_xfer(void *cookie, i2c_addr_t addr, int flags)
+{
+ struct octiic_softc *sc = cookie;
+ int error;
+ uint8_t mode = 0, nstate;
+
+ error = octiic_i2c_send_start(sc, flags);
+ if (error != 0)
+ return error;
+
+ if (flags & I2C_F_READ)
+ mode = 0x01;
+ nstate = flags & I2C_F_READ ? TWSI_STAT_ART_ACK : TWSI_STAT_AWT_ACK;
+
+ /* Handle 10-bit addressing. */
+ if (addr > 0x7f) {
+ octiic_reg_write(sc, TWSI_IA_DATA, ((addr >> 7) << 1) | mode);
+ octiic_reg_write(sc, TWSI_IA_CTL, TWSI_CTL_ENAB);
+
+ error = octiic_wait(sc, nstate, flags);
+ if (error != 0)
+ return error;
+ }
+
+ octiic_reg_write(sc, TWSI_IA_DATA, ((addr & 0x7f) << 1) | mode);
+ octiic_reg_write(sc, TWSI_IA_CTL, TWSI_CTL_ENAB);
+
+ error = octiic_wait(sc, nstate, flags);
+ if (error != 0)
+ return error;
+
+ return 0;
+}
+
+int
+octiic_i2c_read_byte(void *cookie, uint8_t *datap, int flags)
+{
+ struct octiic_softc *sc = cookie;
+ int error;
+ uint8_t ctl, nstate;
+
+ ctl = TWSI_CTL_ENAB;
+ if ((flags & I2C_F_LAST) == 0)
+ ctl |= TWSI_CTL_AAK;
+ octiic_reg_write(sc, TWSI_IA_CTL, ctl);
+
+ nstate = flags & I2C_F_LAST ? TWSI_STAT_MBR_NAK : TWSI_STAT_MBR_ACK;
+ error = octiic_wait(sc, nstate, flags);
+ if (error != 0)
+ return error;
+
+ octiic_reg_read(sc, TWSI_IA_DATA, datap);
+
+ if (flags & I2C_F_STOP)
+ error = octiic_i2c_send_stop(sc, flags);
+
+ return 0;
+}
+
+int
+octiic_i2c_write_byte(void *cookie, uint8_t data, int flags)
+{
+ struct octiic_softc *sc = cookie;
+ int error;
+
+ octiic_reg_write(sc, TWSI_IA_DATA, data);
+ octiic_reg_write(sc, TWSI_IA_CTL, TWSI_CTL_ENAB);
+
+ error = octiic_wait(sc, TWSI_STAT_MBT_ACK, flags);
+ if (error != 0)
+ return error;
+
+ if (flags & I2C_F_STOP)
+ error = octiic_i2c_send_stop(sc, flags);
+
+ return error;
+}
+
+int
+octiic_reg_read(struct octiic_softc *sc, uint8_t reg, uint8_t *pval)
+{
+ uint64_t data;
+ int timeout;
+
+ TWSI_WR_8(sc, TWSI_SW_TWSI, TWSI_SW_TWSI_V | TWSI_SW_TWSI_R |
+ ((uint64_t)TWSI_OP_EOP << TWSI_SW_TWSI_OP_S) |
+ ((uint64_t)reg << TWSI_SW_TWSI_EOP_IA_S));
+
+ for (timeout = 100000; timeout > 0; timeout--) {
+ data = TWSI_RD_8(sc, TWSI_SW_TWSI);
+ if ((data & TWSI_SW_TWSI_V) == 0)
+ break;
+ delay(1);
+ }
+ if (timeout == 0)
+ return ETIMEDOUT;
+
+ *pval = (uint8_t)data;
+ return 0;
+}
+
+int
+octiic_reg_write(struct octiic_softc *sc, uint8_t reg, uint8_t val)
+{
+ uint64_t data;
+ int timeout;
+
+ TWSI_WR_8(sc, TWSI_SW_TWSI, TWSI_SW_TWSI_V |
+ ((uint64_t)TWSI_OP_EOP << TWSI_SW_TWSI_OP_S) |
+ ((uint64_t)reg << TWSI_SW_TWSI_EOP_IA_S) | val);
+
+ for (timeout = 100000; timeout > 0; timeout--) {
+ data = TWSI_RD_8(sc, TWSI_SW_TWSI);
+ if ((data & TWSI_SW_TWSI_V) == 0)
+ break;
+ delay(1);
+ }
+ if (timeout == 0)
+ return ETIMEDOUT;
+
+ return 0;
+}
+
+/*
+ * Wait until the controller has finished current operation.
+ * Fail if the new state is not `nstate'.
+ */
+int
+octiic_wait(struct octiic_softc *sc, uint8_t nstate, int flags)
+{
+ uint8_t ctl, stat;
+ int timeout;
+
+ for (timeout = 100000; timeout > 0; timeout--) {
+ octiic_reg_read(sc, TWSI_IA_CTL, &ctl);
+ if (ctl & TWSI_CTL_IFLG)
+ break;
+ }
+
+ octiic_reg_read(sc, TWSI_IA_STAT, &stat);
+ if (stat != nstate)
+ return EIO;
+
+ return 0;
+}
+
+int
+octiic_set_clock(struct octiic_softc *sc, uint32_t freq)
+{
+ uint64_t best_tclk = 0, tclk;
+ uint64_t ioclk = octeon_ioclock_speed();
+ int best_m = 2, best_n = 0, best_thp = 24;
+ int m, n, thp;
+
+ /*
+ * Find a combination of clock dividers `thp', `m' and `n' that gives
+ * bus frequency close to but no more than `freq'.
+ */
+#define TCLK(ioclk, thp, n, m) \
+ ((ioclk) / (20 * ((thp) + 1) * (1 << (n)) * ((m) + 1)))
+ for (thp = 6; thp <= 72 && best_tclk < freq; thp <<= 1) {
+ for (n = 7; n > 0; n--) {
+ if (TCLK(ioclk, thp, n, 16) > freq)
+ break;
+ }
+ for (m = 15; m > 2; m--) {
+ if (TCLK(ioclk, thp, n, m - 1) > freq)
+ break;
+ }
+
+ tclk = TCLK(ioclk, thp, n, m);
+ if (tclk <= freq && tclk > best_tclk) {
+ best_tclk = tclk;
+ best_thp = thp;
+ best_m = m;
+ best_n = n;
+ }
+ }
+#undef TCLK
+
+ TWSI_WR_8(sc, TWSI_SW_TWSI, TWSI_SW_TWSI_V |
+ ((uint64_t)TWSI_OP_CLK << TWSI_SW_TWSI_OP_S) | best_thp);
+
+ octiic_reg_write(sc, TWSI_IA_CLKCTL, (best_m << 3) | best_n);
+
+ return 0;
+}