From 3dd177aa34519f206bc2bd373c14bb3f3e38d875 Mon Sep 17 00:00:00 2001 From: Emmanuel Vadot Date: Fri, 5 Apr 2019 14:44:23 +0000 Subject: [PATCH] twsi: Add interrupt mode Add the ability to use interrupts for i2c message. We still use polling for early boot i2c transfer (for PMIC for example) but as soon as interrupts are available use them. On Allwinner SoC >A20 is seems that polling mode is broken for some reason, this is now fixed by using interrupt mode. For Allwinner also fix the frequency calculation, the one in the code was for when the APB frequency is at 48Mhz while it is at 24Mhz on most (all?) Allwinner SoCs. We now support both cases. While here add more debug info when it's compiled in. Tested On: A20, H3, A64 MFC after: 1 month --- sys/dev/iicbus/twsi/a10_twsi.c | 35 ++++- sys/dev/iicbus/twsi/twsi.c | 260 ++++++++++++++++++++++++++++++--- sys/dev/iicbus/twsi/twsi.h | 13 +- 3 files changed, 283 insertions(+), 25 deletions(-) diff --git a/sys/dev/iicbus/twsi/a10_twsi.c b/sys/dev/iicbus/twsi/a10_twsi.c index 8822aea94e8e..7eaef02a7071 100644 --- a/sys/dev/iicbus/twsi/a10_twsi.c +++ b/sys/dev/iicbus/twsi/a10_twsi.c @@ -1,6 +1,5 @@ /*- - * Copyright (c) 2016 Emmanuel Vadot - * All rights reserved. + * Copyright (c) 2016-2019 Emmanuel Vadot * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -90,6 +89,7 @@ a10_twsi_attach(device_t dev) struct twsi_softc *sc; clk_t clk; hwreset_t rst; + uint64_t freq; int error; sc = device_get_softc(dev); @@ -124,10 +124,31 @@ a10_twsi_attach(device_t dev) sc->reg_soft_reset = TWI_SRST; /* Setup baud rate params */ - sc->baud_rate[IIC_SLOW].param = TWSI_BAUD_RATE_PARAM(11, 2); - sc->baud_rate[IIC_FAST].param = TWSI_BAUD_RATE_PARAM(11, 2); - sc->baud_rate[IIC_FASTEST].param = TWSI_BAUD_RATE_PARAM(2, 2); + clk_get_freq(clk, &freq); + switch (freq) { + /* + * Formula is + * F0 = FINT / 2 ^ CLK_N + * F1 = F0 / (CLK_M + 1) + * + * Doc says that the output freq is F1/10 but my logic analyzer says otherwise + */ + case 48000000: + sc->baud_rate[IIC_SLOW].param = TWSI_BAUD_RATE_PARAM(11, 1); + sc->baud_rate[IIC_FAST].param = TWSI_BAUD_RATE_PARAM(11, 1); + sc->baud_rate[IIC_FASTEST].param = TWSI_BAUD_RATE_PARAM(2, 1); + break; + case 24000000: + sc->baud_rate[IIC_SLOW].param = TWSI_BAUD_RATE_PARAM(5, 2); + sc->baud_rate[IIC_FAST].param = TWSI_BAUD_RATE_PARAM(5, 2); + sc->baud_rate[IIC_FASTEST].param = TWSI_BAUD_RATE_PARAM(2, 2); + break; + default: + device_printf(dev, "Non supported frequency\n"); + return (ENXIO); + } + sc->need_ack = true; return (twsi_attach(dev)); } @@ -154,7 +175,7 @@ DEFINE_CLASS_1(iichb, a10_twsi_driver, a10_twsi_methods, static devclass_t a10_twsi_devclass; EARLY_DRIVER_MODULE(a10_twsi, simplebus, a10_twsi_driver, a10_twsi_devclass, - 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); + 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); EARLY_DRIVER_MODULE(iicbus, a10_twsi, iicbus_driver, iicbus_devclass, - 0, 0, BUS_PASS_BUS + BUS_PASS_ORDER_MIDDLE); + 0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE); MODULE_DEPEND(a10_twsi, iicbus, 1, 1, 1); diff --git a/sys/dev/iicbus/twsi/twsi.c b/sys/dev/iicbus/twsi/twsi.c index 39abe5e67c97..6eaecba99be4 100644 --- a/sys/dev/iicbus/twsi/twsi.c +++ b/sys/dev/iicbus/twsi/twsi.c @@ -31,7 +31,7 @@ /* * Driver for the TWSI (aka I2C, aka IIC) bus controller found on Marvell - * and Allwinner SoCs. Supports master operation only, and works in polling mode. + * and Allwinner SoCs. Supports master operation only. * * Calls to DELAY() are needed per Application Note AN-179 "TWSI Software * Guidelines for Discovery(TM), Horizon (TM) and Feroceon(TM) Devices". @@ -75,8 +75,11 @@ __FBSDID("$FreeBSD$"); #define TWSI_STATUS_START 0x08 #define TWSI_STATUS_RPTD_START 0x10 #define TWSI_STATUS_ADDR_W_ACK 0x18 +#define TWSI_STATUS_ADDR_W_NACK 0x20 #define TWSI_STATUS_DATA_WR_ACK 0x28 +#define TWSI_STATUS_DATA_WR_NACK 0x30 #define TWSI_STATUS_ADDR_R_ACK 0x40 +#define TWSI_STATUS_ADDR_R_NACK 0x48 #define TWSI_STATUS_DATA_RD_ACK 0x50 #define TWSI_STATUS_DATA_RD_NOACK 0x58 @@ -84,27 +87,32 @@ __FBSDID("$FreeBSD$"); #undef TWSI_DEBUG #ifdef TWSI_DEBUG -#define debugf(fmt, args...) do { printf("%s(): ", __func__); printf(fmt,##args); } while (0) +#define debugf(dev, fmt, args...) device_printf(dev, "%s: " fmt, __func__, ##args) #else -#define debugf(fmt, args...) +#define debugf(dev, fmt, args...) #endif static struct resource_spec res_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE}, { -1, 0 } }; static __inline uint32_t TWSI_READ(struct twsi_softc *sc, bus_size_t off) { + uint32_t val; - return (bus_read_4(sc->res[0], off)); + val = bus_read_4(sc->res[0], off); + debugf(sc->dev, "read %x from %lx\n", val, off); + return (val); } static __inline void TWSI_WRITE(struct twsi_softc *sc, bus_size_t off, uint32_t val) { + debugf(sc->dev, "Writing %x to %lx\n", val, off); bus_write_4(sc->res[0], off, val); } @@ -114,8 +122,10 @@ twsi_control_clear(struct twsi_softc *sc, uint32_t mask) uint32_t val; val = TWSI_READ(sc, sc->reg_control); + debugf(sc->dev, "read val=%x\n", val); val &= ~(TWSI_CONTROL_STOP | TWSI_CONTROL_START); val &= ~mask; + debugf(sc->dev, "write val=%x\n", val); TWSI_WRITE(sc, sc->reg_control, val); } @@ -125,8 +135,10 @@ twsi_control_set(struct twsi_softc *sc, uint32_t mask) uint32_t val; val = TWSI_READ(sc, sc->reg_control); + debugf(sc->dev, "read val=%x\n", val); val &= ~(TWSI_CONTROL_STOP | TWSI_CONTROL_START); val |= mask; + debugf(sc->dev, "write val=%x\n", val); TWSI_WRITE(sc, sc->reg_control, val); } @@ -151,11 +163,13 @@ twsi_poll_ctrl(struct twsi_softc *sc, int timeout, uint32_t mask) { timeout /= 10; + debugf(sc->dev, "Waiting for ctrl reg to match mask %x\n", mask); while (!(TWSI_READ(sc, sc->reg_control) & mask)) { DELAY(10); if (--timeout < 0) return (timeout); } + debugf(sc->dev, "done\n"); return (0); } @@ -179,10 +193,11 @@ twsi_locked_start(device_t dev, struct twsi_softc *sc, int32_t mask, /* read IFLG to know if it should be cleared later; from NBSD */ iflg_set = TWSI_READ(sc, sc->reg_control) & TWSI_CONTROL_IFLG; + debugf(dev, "send start\n"); twsi_control_set(sc, TWSI_CONTROL_START); if (mask == TWSI_STATUS_RPTD_START && iflg_set) { - debugf("IFLG set, clearing\n"); + debugf(dev, "IFLG set, clearing (mask=%x)\n", mask); twsi_clear_iflg(sc); } @@ -193,14 +208,16 @@ twsi_locked_start(device_t dev, struct twsi_softc *sc, int32_t mask, DELAY(1000); if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { - debugf("timeout sending %sSTART condition\n", + debugf(dev, "timeout sending %sSTART condition\n", mask == TWSI_STATUS_START ? "" : "repeated "); return (IIC_ETIMEOUT); } status = TWSI_READ(sc, sc->reg_status); + debugf(dev, "status=%x\n", status); + if (status != mask) { - debugf("wrong status (%02x) after sending %sSTART condition\n", + debugf(dev, "wrong status (%02x) after sending %sSTART condition\n", status, mask == TWSI_STATUS_START ? "" : "repeated "); return (IIC_ESTATUS); } @@ -210,15 +227,15 @@ twsi_locked_start(device_t dev, struct twsi_softc *sc, int32_t mask, DELAY(1000); if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { - debugf("timeout sending slave address\n"); + debugf(dev, "timeout sending slave address (timeout=%d)\n", timeout); return (IIC_ETIMEOUT); } - + read_access = (slave & 0x1) ? 1 : 0; status = TWSI_READ(sc, sc->reg_status); if (status != (read_access ? TWSI_STATUS_ADDR_R_ACK : TWSI_STATUS_ADDR_W_ACK)) { - debugf("no ACK (status: %02x) after sending slave address\n", + debugf(dev, "no ACK (status: %02x) after sending slave address\n", status); return (IIC_ENOACK); } @@ -234,6 +251,7 @@ twsi_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct twsi_softc *sc; uint32_t param; + /* uint32_t val; */ sc = device_get_softc(dev); @@ -241,17 +259,18 @@ twsi_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) case IIC_SLOW: case IIC_FAST: param = sc->baud_rate[speed].param; + debugf(dev, "Using IIC_FAST mode with speed param=%x\n", param); break; case IIC_FASTEST: case IIC_UNKNOWN: default: param = sc->baud_rate[IIC_FAST].param; + debugf(dev, "Using IIC_FASTEST/UNKNOWN mode with speed param=%x\n", param); break; } mtx_lock(&sc->mutex); TWSI_WRITE(sc, sc->reg_soft_reset, 0x0); - DELAY(2000); TWSI_WRITE(sc, sc->reg_baud_rate, param); TWSI_WRITE(sc, sc->reg_control, TWSI_CONTROL_TWSIEN); DELAY(1000); @@ -267,6 +286,7 @@ twsi_stop(device_t dev) sc = device_get_softc(dev); + debugf(dev, "%s\n", __func__); mtx_lock(&sc->mutex); twsi_control_clear(sc, TWSI_CONTROL_ACK); twsi_control_set(sc, TWSI_CONTROL_STOP); @@ -288,6 +308,7 @@ twsi_repeated_start(device_t dev, u_char slave, int timeout) sc = device_get_softc(dev); + debugf(dev, "%s: slave=%x\n", __func__, slave); mtx_lock(&sc->mutex); rv = twsi_locked_start(dev, sc, TWSI_STATUS_RPTD_START, slave, timeout); @@ -311,6 +332,7 @@ twsi_start(device_t dev, u_char slave, int timeout) sc = device_get_softc(dev); + debugf(dev, "%s: slave=%x\n", __func__, slave); mtx_lock(&sc->mutex); rv = twsi_locked_start(dev, sc, TWSI_STATUS_START, slave, timeout); mtx_unlock(&sc->mutex); @@ -348,7 +370,7 @@ twsi_read(device_t dev, char *buf, int len, int *read, int last, int delay) DELAY(1000); if (twsi_poll_ctrl(sc, delay, TWSI_CONTROL_IFLG)) { - debugf("timeout reading data\n"); + debugf(dev, "timeout reading data (delay=%d)\n", delay); rv = IIC_ETIMEOUT; goto out; } @@ -356,7 +378,7 @@ twsi_read(device_t dev, char *buf, int len, int *read, int last, int delay) status = TWSI_READ(sc, sc->reg_status); if (status != (last_byte ? TWSI_STATUS_DATA_RD_NOACK : TWSI_STATUS_DATA_RD_ACK)) { - debugf("wrong status (%02x) while reading\n", status); + debugf(dev, "wrong status (%02x) while reading\n", status); rv = IIC_ESTATUS; goto out; } @@ -387,14 +409,14 @@ twsi_write(device_t dev, const char *buf, int len, int *sent, int timeout) twsi_clear_iflg(sc); DELAY(1000); if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { - debugf("timeout writing data\n"); + debugf(dev, "timeout writing data (timeout=%d)\n", timeout); rv = IIC_ETIMEOUT; goto out; } status = TWSI_READ(sc, sc->reg_status); if (status != TWSI_STATUS_DATA_WR_ACK) { - debugf("wrong status (%02x) while writing\n", status); + debugf(dev, "wrong status (%02x) while writing\n", status); rv = IIC_ESTATUS; goto out; } @@ -406,6 +428,202 @@ twsi_write(device_t dev, const char *buf, int len, int *sent, int timeout) return (rv); } +static int +twsi_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) +{ + struct twsi_softc *sc; + int i; + + sc = device_get_softc(dev); + + if (sc->have_intr == false) + return (iicbus_transfer_gen(dev, msgs, nmsgs)); + + sc->error = 0; + + sc->control_val = TWSI_CONTROL_TWSIEN | + TWSI_CONTROL_INTEN | TWSI_CONTROL_ACK; + TWSI_WRITE(sc, sc->reg_control, sc->control_val); + debugf(dev, "transmitting %d messages\n", nmsgs); + debugf(sc->dev, "status=%x\n", TWSI_READ(sc, sc->reg_status)); + for (i = 0; i < nmsgs && sc->error == 0; i++) { + sc->transfer = 1; + sc->msg = &msgs[i]; + debugf(dev, "msg[%d] flags: %x\n", i, msgs[i].flags); + debugf(dev, "msg[%d] len: %d\n", i, msgs[i].len); + + /* Send start and re-enable interrupts */ + sc->control_val = TWSI_CONTROL_TWSIEN | + TWSI_CONTROL_INTEN | TWSI_CONTROL_ACK; + if (sc->msg->len == 1) + sc->control_val &= ~TWSI_CONTROL_ACK; + TWSI_WRITE(sc, sc->reg_control, sc->control_val | TWSI_CONTROL_START); + while (sc->error == 0 && sc->transfer != 0) { + pause_sbt("twsi", SBT_1MS * 30, SBT_1MS, 0); + } + + debugf(dev, "Done with msg[%d]\n", i); + if (sc->error) { + debugf(sc->dev, "Error, aborting (%d)\n", sc->error); + TWSI_WRITE(sc, sc->reg_control, 0); + goto out; + } + } + + /* Disable module and interrupts */ + debugf(sc->dev, "status=%x\n", TWSI_READ(sc, sc->reg_status)); + TWSI_WRITE(sc, sc->reg_control, 0); + debugf(sc->dev, "status=%x\n", TWSI_READ(sc, sc->reg_status)); + +out: + return (sc->error); +} + +static void +twsi_intr(void *arg) +{ + struct twsi_softc *sc; + uint32_t status; + int transfer_done = 0; + + sc = arg; + + debugf(sc->dev, "Got interrupt\n"); + + while (TWSI_READ(sc, sc->reg_control) & TWSI_CONTROL_IFLG) { + status = TWSI_READ(sc, sc->reg_status); + debugf(sc->dev, "status=%x\n", status); + + switch (status) { + case TWSI_STATUS_START: + case TWSI_STATUS_RPTD_START: + /* Transmit the address */ + debugf(sc->dev, "Send the address\n"); + + if (sc->msg->flags & IIC_M_RD) + TWSI_WRITE(sc, sc->reg_data, + sc->msg->slave | LSB); + else + TWSI_WRITE(sc, sc->reg_data, + sc->msg->slave & ~LSB); + + TWSI_WRITE(sc, sc->reg_control, sc->control_val); + break; + + case TWSI_STATUS_ADDR_W_ACK: + debugf(sc->dev, "Ack received after transmitting the address\n"); + /* Directly send the first byte */ + sc->sent_bytes = 0; + debugf(sc->dev, "Sending byte 0 = %x\n", sc->msg->buf[0]); + TWSI_WRITE(sc, sc->reg_data, sc->msg->buf[0]); + + TWSI_WRITE(sc, sc->reg_control, sc->control_val); + break; + + case TWSI_STATUS_ADDR_R_ACK: + sc->recv_bytes = 0; + + TWSI_WRITE(sc, sc->reg_control, sc->control_val); + break; + + case TWSI_STATUS_ADDR_W_NACK: + case TWSI_STATUS_ADDR_R_NACK: + debugf(sc->dev, "No ack received after transmitting the address\n"); + sc->transfer = 0; + sc->error = ETIMEDOUT; + sc->control_val = 0; + wakeup(sc); + break; + + case TWSI_STATUS_DATA_WR_ACK: + debugf(sc->dev, "Ack received after transmitting data\n"); + if (sc->sent_bytes++ == (sc->msg->len - 1)) { + debugf(sc->dev, "Done sending all the bytes\n"); + /* Send stop, no interrupts on stop */ + if (!(sc->msg->flags & IIC_M_NOSTOP)) { + debugf(sc->dev, "Done TX data, send stop\n"); + TWSI_WRITE(sc, sc->reg_control, + sc->control_val | TWSI_CONTROL_STOP); + } else { + sc->control_val &= ~TWSI_CONTROL_INTEN; + TWSI_WRITE(sc, sc->reg_control, + sc->control_val); + } + transfer_done = 1; + } else { + debugf(sc->dev, "Sending byte %d = %x\n", + sc->sent_bytes, + sc->msg->buf[sc->sent_bytes]); + TWSI_WRITE(sc, sc->reg_data, + sc->msg->buf[sc->sent_bytes]); + TWSI_WRITE(sc, sc->reg_control, + sc->control_val); + } + + break; + case TWSI_STATUS_DATA_RD_ACK: + debugf(sc->dev, "Ack received after receiving data\n"); + debugf(sc->dev, "msg_len=%d recv_bytes=%d\n", sc->msg->len, sc->recv_bytes); + sc->msg->buf[sc->recv_bytes++] = TWSI_READ(sc, sc->reg_data); + + /* If we only have one byte left, disable ACK */ + if (sc->msg->len - sc->recv_bytes == 1) + sc->control_val &= ~TWSI_CONTROL_ACK; + TWSI_WRITE(sc, sc->reg_control, sc->control_val); + break; + + case TWSI_STATUS_DATA_RD_NOACK: + if (sc->msg->len - sc->recv_bytes == 1) { + sc->msg->buf[sc->recv_bytes++] = TWSI_READ(sc, sc->reg_data); + debugf(sc->dev, "Done RX data, send stop (2)\n"); + if (!(sc->msg->flags & IIC_M_NOSTOP)) + TWSI_WRITE(sc, sc->reg_control, + sc->control_val | TWSI_CONTROL_STOP); + } else { + debugf(sc->dev, "No ack when receiving data\n"); + sc->error = ENXIO; + sc->control_val = 0; + } + sc->transfer = 0; + transfer_done = 1; + break; + + default: + debugf(sc->dev, "status=%x hot handled\n", status); + sc->transfer = 0; + sc->error = ENXIO; + sc->control_val = 0; + wakeup(sc); + break; + } + + if (sc->need_ack) + TWSI_WRITE(sc, sc->reg_control, + sc->control_val | TWSI_CONTROL_IFLG); + } + + debugf(sc->dev, "Done with interrupts\n"); + if (transfer_done == 1) { + sc->transfer = 0; + wakeup(sc); + } +} + +static void +twsi_intr_start(void *pdev) +{ + struct twsi_softc *sc; + + sc = device_get_softc(pdev); + + if ((bus_setup_intr(pdev, sc->res[1], INTR_TYPE_MISC | INTR_MPSAFE, + NULL, twsi_intr, sc, &sc->intrhand))) + device_printf(pdev, "unable to register interrupt handler\n"); + + sc->have_intr = true; + config_intrhook_disestablish(&sc->intr_hook); +} + int twsi_attach(device_t dev) { @@ -430,6 +648,12 @@ twsi_attach(device_t dev) } bus_generic_attach(dev); + sc->intr_hook.ich_func = twsi_intr_start; + sc->intr_hook.ich_arg = dev; + + if (config_intrhook_establish(&sc->intr_hook) != 0) + return (ENOMEM); + return (0); } @@ -440,7 +664,6 @@ twsi_detach(device_t dev) int rv; sc = device_get_softc(dev); - debugf(""); if ((rv = bus_generic_detach(dev)) != 0) return (rv); @@ -449,6 +672,9 @@ twsi_detach(device_t dev) if ((rv = device_delete_child(dev, sc->iicbus)) != 0) return (rv); + if (sc->intrhand != NULL) + bus_teardown_intr(sc->dev, sc->res[1], sc->intrhand); + bus_release_resources(dev, res_spec, sc->res); mtx_destroy(&sc->mutex); @@ -478,7 +704,7 @@ static device_method_t twsi_methods[] = { DEVMETHOD(iicbus_write, twsi_write), DEVMETHOD(iicbus_read, twsi_read), DEVMETHOD(iicbus_reset, twsi_reset), - DEVMETHOD(iicbus_transfer, iicbus_transfer_gen), + DEVMETHOD(iicbus_transfer, twsi_transfer), { 0, 0 } }; diff --git a/sys/dev/iicbus/twsi/twsi.h b/sys/dev/iicbus/twsi/twsi.h index c10f82269d55..25a27e514819 100644 --- a/sys/dev/iicbus/twsi/twsi.h +++ b/sys/dev/iicbus/twsi/twsi.h @@ -47,13 +47,24 @@ struct twsi_baud_rate { struct twsi_softc { device_t dev; - struct resource *res[1]; /* SYS_RES_MEMORY */ + struct resource *res[2]; struct mtx mutex; device_t iicbus; #ifdef EXT_RESOURCES clk_t clk_core; clk_t clk_reg; #endif + void * intrhand; + struct intr_config_hook intr_hook; + bool have_intr; + + struct iic_msg *msg; + uint16_t sent_bytes; + uint16_t recv_bytes; + int transfer; + int error; + uint32_t control_val; + bool need_ack; bus_size_t reg_data; bus_size_t reg_slave_addr;