/* $OpenBSD: octiic.c,v 1.3 2020/09/10 16:40:40 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 #include #include #include #include #include #include #define _I2C_PRIVATE #include #include #include #include struct octiic_softc { struct device sc_dev; int sc_node; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; struct i2c_bus sc_i2c_bus; 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; iba.iba_bus_scan = octiic_i2c_scan; iba.iba_bus_scan_arg = sc; config_found(self, &iba, iicbus_print); sc->sc_i2c_bus.ib_node = sc->sc_node; sc->sc_i2c_bus.ib_ic = &sc->sc_i2c_tag; i2c_register(&sc->sc_i2c_bus); } 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; } void octiic_i2c_scan(struct device *self, struct i2cbus_attach_args *iba, void *arg) { struct i2c_attach_args ia; char name[32]; uint32_t reg[1]; struct octiic_softc *sc = arg; int node; for (node = OF_child(sc->sc_node); node != 0; node = OF_peer(node)) { memset(name, 0, sizeof(name)); memset(reg, 0, sizeof(reg)); if (OF_getprop(node, "compatible", name, sizeof(name)) == -1) continue; if (name[0] == '\0') continue; if (OF_getprop(node, "reg", ®, sizeof(reg)) != sizeof(reg)) continue; memset(&ia, 0, sizeof(ia)); ia.ia_tag = iba->iba_tag; ia.ia_addr = reg[0]; ia.ia_name = name; ia.ia_cookie = &node; config_found(self, &ia, iic_print); } } 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; }