/* $NetBSD: jziic.c,v 1.9 2021/08/07 16:18:59 thorpej Exp $ */ /*- * Copyright (c) 2015 Michael Lorenz * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __KERNEL_RCSID(0, "$NetBSD: jziic.c,v 1.9 2021/08/07 16:18:59 thorpej Exp $"); /* * a preliminary driver for JZ4780's on-chip SMBus controllers * - needs more error handling and interrupt support * - transfers can't be more than the chip's FIFO, supposedly 16 bytes per * direction * so, good enough for RTCs but not much else yet */ #include #include #include #include #include #include #include #include #include #include #include #include "opt_ingenic.h" #ifdef JZIIC_DEBUG #define DPRINTF aprint_error #define STATIC /* */ #else #define DPRINTF while (0) printf #define STATIC static #endif STATIC int jziic_match(device_t, struct cfdata *, void *); STATIC void jziic_attach(device_t, device_t, void *); struct jziic_softc { device_t sc_dev; bus_space_tag_t sc_memt; bus_space_handle_t sc_memh; struct i2c_controller sc_i2c; kmutex_t sc_cvlock; uint32_t sc_pclk; /* stuff used for interrupt-driven transfers */ const uint8_t *sc_cmd; uint8_t *sc_buf; uint32_t sc_cmdlen, sc_buflen; uint32_t sc_cmdptr, sc_bufptr, sc_rds; uint32_t sc_abort; kcondvar_t sc_ping; uint8_t sc_txbuf[256]; boolean_t sc_reading; }; CFATTACH_DECL_NEW(jziic, sizeof(struct jziic_softc), jziic_match, jziic_attach, NULL, NULL); STATIC int jziic_enable(struct jziic_softc *); STATIC void jziic_disable(struct jziic_softc *); STATIC int jziic_wait(struct jziic_softc *); STATIC void jziic_set_speed(struct jziic_softc *); STATIC int jziic_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t, void *, size_t, int); STATIC int jziic_i2c_exec_poll(struct jziic_softc *, i2c_op_t, i2c_addr_t, const void *, size_t, void *, size_t, int); STATIC int jziic_i2c_exec_intr(struct jziic_softc *, i2c_op_t, i2c_addr_t, const void *, size_t, void *, size_t, int); STATIC int jziic_intr(void *); /* ARGSUSED */ STATIC int jziic_match(device_t parent, struct cfdata *match, void *aux) { struct apbus_attach_args *aa = aux; if (strcmp(aa->aa_name, "jziic") != 0) return 0; return 1; } /* ARGSUSED */ STATIC void jziic_attach(device_t parent, device_t self, void *aux) { struct jziic_softc *sc = device_private(self); struct apbus_attach_args *aa = aux; struct i2cbus_attach_args iba; int error; void *ih; #ifdef JZIIC_DEBUG int i; uint8_t in[1] = {0}, out[16]; #endif sc->sc_dev = self; sc->sc_pclk = aa->aa_pclk; sc->sc_memt = aa->aa_bst; error = bus_space_map(aa->aa_bst, aa->aa_addr, 0x100, 0, &sc->sc_memh); if (error) { aprint_error_dev(self, "can't map registers for %s: %d\n", aa->aa_name, error); return; } mutex_init(&sc->sc_cvlock, MUTEX_DEFAULT, IPL_NONE); cv_init(&sc->sc_ping, device_xname(self)); aprint_naive(": SMBus controller\n"); aprint_normal(": SMBus controller\n"); ih = evbmips_intr_establish(aa->aa_irq, jziic_intr, sc); if (ih == NULL) { aprint_error_dev(self, "failed to establish interrupt %d\n", aa->aa_irq); goto fail; } #ifdef JZIIC_DEBUG if (jziic_i2c_exec(sc, I2C_OP_READ_WITH_STOP, 0x51, in, 1, out, 9, 0) >= 0) { for (i = 0; i < 9; i++) printf(" %02x", out[i]); printf("\n"); delay(1000000); jziic_i2c_exec(sc, I2C_OP_READ_WITH_STOP, 0x51, in, 1, out, 9, 0); for (i = 0; i < 9; i++) printf(" %02x", out[i]); printf("\n"); delay(1000000); jziic_i2c_exec(sc, I2C_OP_READ_WITH_STOP, 0x51, in, 1, out, 9, 0); for (i = 0; i < 9; i++) printf(" %02x", out[i]); printf("\n"); } #endif /* fill in the i2c tag */ iic_tag_init(&sc->sc_i2c); sc->sc_i2c.ic_cookie = sc; sc->sc_i2c.ic_exec = jziic_i2c_exec; memset(&iba, 0, sizeof(iba)); iba.iba_tag = &sc->sc_i2c; config_found(sc->sc_dev, &iba, iicbus_print, CFARGS_NONE); return; fail: if (ih) { evbmips_intr_disestablish(ih); } bus_space_unmap(sc->sc_memt, sc->sc_memh, 0x100); } STATIC int jziic_enable(struct jziic_softc *sc) { int bail = 100000; uint32_t reg; bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBENB, JZ_ENABLE); reg = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBENBST); DPRINTF("status: %02x\n", reg); while ((bail > 0) && (reg == 0)) { bail--; reg = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBENBST); } DPRINTF("bail: %d\n", bail); return (reg != 0); } STATIC void jziic_disable(struct jziic_softc *sc) { int bail = 100000; uint32_t reg; bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBENB, 0); reg = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBENBST); DPRINTF("status: %02x\n", reg); while ((bail > 0) && (reg != 0)) { bail--; reg = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBENBST); } DPRINTF("bail: %d\n", bail); } STATIC int jziic_wait(struct jziic_softc *sc) { uint32_t reg; int bail = 10000; reg = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBST); while ((reg & JZ_MSTACT) && (bail > 0)) { delay(100); reg = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBST); bail--; } return ((reg & JZ_MSTACT) == 0); } STATIC void jziic_set_speed(struct jziic_softc *sc) { int ticks, hcnt, lcnt, hold, setup; /* PCLK ticks per SMBus cycle */ ticks = sc->sc_pclk / 100; /* assuming 100kHz for now */ hcnt = (ticks * 40 / (40 + 47)) - 8; lcnt = (ticks * 47 / (40 + 47)) - 1; hold = sc->sc_pclk * 4 / 10000 - 1; /* ... * 400 / 1000000 ... */ hold = uimax(1, hold); hold |= JZ_HDENB; setup = sc->sc_pclk * 3 / 10000 + 1; /* ... * 300 / 1000000 ... */ DPRINTF("hcnt %d lcnt %d hold %d\n", hcnt, lcnt, hold); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBSHCNT, hcnt); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBSLCNT, lcnt); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBSDAHD, hold); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBSDASU, setup); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBCON, JZ_SLVDIS | JZ_STPHLD | JZ_REST | JZ_SPD_100KB | JZ_MD); (void)bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBCINT); } STATIC int jziic_i2c_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *vcmd, size_t cmdlen, void *vbuf, size_t buflen, int flags) { struct jziic_softc *sc = cookie; if (flags & I2C_F_POLL) { return jziic_i2c_exec_poll(sc, op, addr, vcmd, cmdlen, vbuf, buflen, flags); } else { #ifdef JZIIC_DEBUG uint8_t *b = vbuf; int i, ret; memset(vbuf, 0, buflen); jziic_i2c_exec_intr(sc, op, addr, vcmd, cmdlen, vbuf, buflen, flags); for (i = 0; i < buflen; i++) { printf(" %02x", b[i]); } printf("\n"); ret = jziic_i2c_exec_poll(sc, op, addr, vcmd, cmdlen, vbuf, buflen, flags); for (i = 0; i < buflen; i++) { printf(" %02x", b[i]); } printf("\n"); return ret; #else return jziic_i2c_exec_intr(sc, op, addr, vcmd, cmdlen, vbuf, buflen, flags); #endif } } STATIC int jziic_i2c_exec_poll(struct jziic_softc *sc, i2c_op_t op, i2c_addr_t addr, const void *vcmd, size_t cmdlen, void *vbuf, size_t buflen, int flags) { int i, bail = 10000, ret = 0; uint32_t abort; uint8_t *rx, data; const uint8_t *tx; tx = vcmd; rx = vbuf; DPRINTF("%s: 0x%02x %d %d\n", __func__, addr, cmdlen, buflen); jziic_disable(sc); /* we're polling, so disable interrupts */ bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTM, 0); jziic_set_speed(sc); jziic_wait(sc); /* try to talk... */ if (!jziic_enable(sc)) { ret = -1; goto bork; } bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTM, 0); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBTAR, addr); jziic_wait(sc); DPRINTF("st: %02x\n", bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBST)); DPRINTF("wr int: %02x\n", bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTST)); abort = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBABTSRC); DPRINTF("abort: %02x\n", abort); if ((abort != 0)) { ret = -1; goto bork; } do { bail--; delay(100); } while (((bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBST) & JZ_TFE) == 0) && (bail > 0)); if (cmdlen != 0) { for (i = 0; i < cmdlen; i++) { bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBDC, *tx); tx++; } } if (I2C_OP_READ_P(op)) { /* now read */ for (i = 0; i < (buflen + 1); i++) { bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBDC, JZ_CMD); } wbflush(); DPRINTF("rd st: %02x\n", bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBST)); DPRINTF("rd int: %02x\n", bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTST)); DPRINTF("abort: %02x\n", bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBABTSRC)); for (i = 0; i < buflen; i++) { bail = 10000; while (((bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBST) & JZ_RFNE) == 0) && (bail > 0)) { bail--; delay(100); } if (bail == 0) { ret = -1; goto bork; } data = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBDC); DPRINTF("rd st: %02x %d\n", bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBST), bail); DPRINTF("rd int: %02x\n", bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTST)); DPRINTF("abort: %02x\n", abort); DPRINTF("rd data: %02x\n", data); *rx = data; rx++; } } else { tx = vbuf; for (i = 0; i < buflen; i++) { DPRINTF("wr data: %02x\n", *tx); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBDC, *tx); wbflush(); tx++; } jziic_wait(sc); abort = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBABTSRC); DPRINTF("abort: %02x\n", abort); if ((abort != 0)) { ret = -1; goto bork; } DPRINTF("st: %02x %d\n", bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBST), bail); DPRINTF("wr int: %02x\n", bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTST)); } bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBCON, JZ_SLVDIS | JZ_REST | JZ_SPD_100KB | JZ_MD); bork: jziic_disable(sc); return ret; } STATIC int jziic_i2c_exec_intr(struct jziic_softc *sc, i2c_op_t op, i2c_addr_t addr, const void *vcmd, size_t cmdlen, void *vbuf, size_t buflen, int flags) { int i, ret = 0, bail; DPRINTF("%s: 0x%02x %d %d\n", __func__, addr, cmdlen, buflen); jziic_disable(sc); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTM, 0); mutex_enter(&sc->sc_cvlock); sc->sc_reading = FALSE; if (I2C_OP_READ_P(op)) { sc->sc_cmd = vcmd; sc->sc_cmdlen = cmdlen; sc->sc_buf = vbuf; sc->sc_buflen = buflen; memset(vbuf, 0, buflen); } else { if ((cmdlen + buflen) > 256) return -1; memcpy(sc->sc_txbuf, vcmd, cmdlen); memcpy(sc->sc_txbuf + cmdlen, vbuf, buflen); sc->sc_cmd = sc->sc_txbuf; sc->sc_cmdlen = cmdlen + buflen; sc->sc_buf = NULL; sc->sc_buflen = 0; } sc->sc_cmdptr = 0; sc->sc_bufptr = 0; sc->sc_rds = 0; sc->sc_abort = 0; jziic_set_speed(sc); jziic_wait(sc); /* set FIFO levels */ bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBTXTL, 4); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBRXTL, 0 /*min(7, max(0, buflen - 2 ))*/); /* try to talk... */ if (!jziic_enable(sc)) { ret = -1; goto bork; } bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTM, 0); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBTAR, addr); jziic_wait(sc); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBCINT, JZ_CLEARALL); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTM, JZ_TXABT | JZ_TXEMP); bail = 100 * sc->sc_cmdlen; while ((sc->sc_cmdptr < sc->sc_cmdlen) && (bail > 0)) { cv_timedwait(&sc->sc_ping, &sc->sc_cvlock, 1); if (sc->sc_abort) { /* we received an abort interrupt -> bailout */ DPRINTF("abort: %x\n", sc->sc_abort); ret = -1; goto bork; } bail--; } if (sc->sc_cmdptr < sc->sc_cmdlen) { /* we didn't send everything? */ DPRINTF("sent %d of %d\n", sc->sc_cmdptr, sc->sc_cmdlen); ret = -1; goto bork; } if (I2C_OP_READ_P(op)) { /* now read */ sc->sc_reading = TRUE; bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTM, JZ_TXABT | JZ_RXFL | JZ_TXEMP); for (i = 0; i < uimin((buflen + 1), 4); i++) { bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBDC, JZ_CMD); wbflush(); } sc->sc_rds = i; bail = 10 * sc->sc_buflen; /* 10 ticks per byte should be ok */ while ((sc->sc_bufptr < sc->sc_buflen) && (bail > 0)) { cv_timedwait(&sc->sc_ping, &sc->sc_cvlock, 1); if (sc->sc_abort) { /* we received an abort interrupt -> bailout */ DPRINTF("rx abort: %x\n", sc->sc_abort); ret = -1; goto bork; } bail--; } if (sc->sc_bufptr < sc->sc_buflen) { /* we didn't get everything? */ DPRINTF("rcvd %d of %d\n", sc->sc_bufptr, sc->sc_buflen); ret = -1; goto bork; } } bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBCON, JZ_SLVDIS | JZ_REST | JZ_SPD_100KB | JZ_MD); bork: bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTM, 0); jziic_disable(sc); mutex_exit(&sc->sc_cvlock); return ret; } STATIC int jziic_intr(void *cookie) { struct jziic_softc *sc = cookie; uint32_t stat, data, rstat; int i; stat = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTST); if (stat & JZ_TXEMP) { if (sc->sc_reading) { if (sc->sc_rds < (sc->sc_buflen + 1)) { for (i = 0; i < uimin(4, (sc->sc_buflen + 1) - sc->sc_rds); i++) { bus_space_write_4( sc->sc_memt, sc->sc_memh, JZ_SMBDC, JZ_CMD); wbflush(); } sc->sc_rds += i; } else { /* we're done, so turn TX FIFO interrupt off */ bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTM, JZ_TXABT | JZ_RXFL); } } else { rstat = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBST); while ((rstat & JZ_TFNF) && (sc->sc_cmdptr < sc->sc_cmdlen)) { data = *sc->sc_cmd; sc->sc_cmd++; sc->sc_cmdptr++; bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBDC, data & 0xff); rstat = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBST); }; /* no need to clear this one */ if (sc->sc_cmdptr >= sc->sc_cmdlen) { cv_signal(&sc->sc_ping); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTM, JZ_TXABT); } } } if (stat & JZ_RXFL) { rstat = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBST); while ((rstat & JZ_RFNE) && (sc->sc_bufptr < sc->sc_buflen)) { data = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBDC); *sc->sc_buf = (uint8_t)(data & 0xff); sc->sc_buf++; sc->sc_bufptr++; rstat = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBST); } if (sc->sc_bufptr >= sc->sc_buflen) cv_signal(&sc->sc_ping); } if (stat & JZ_TXABT) { sc->sc_abort = bus_space_read_4(sc->sc_memt, sc->sc_memh, JZ_SMBABTSRC); cv_signal(&sc->sc_ping); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBCINT, JZ_CLEARALL); bus_space_write_4(sc->sc_memt, sc->sc_memh, JZ_SMBINTM, 0); } return 0; }