Date: Mon, 5 Aug 2019 17:06:20 +0000 (UTC) From: Emmanuel Vadot <manu@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-12@freebsd.org Subject: svn commit: r350596 - stable/12/sys/dev/iicbus/twsi Message-ID: <201908051706.x75H6KBO074578@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: manu Date: Mon Aug 5 17:06:20 2019 New Revision: 350596 URL: https://svnweb.freebsd.org/changeset/base/350596 Log: MFC r345948, r345951 r345948: 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 r345951: twsi: Use config_intrhook_oneshot instead of config_intrhook_establish Suggested by: ian X-MFC-With: 345948 Modified: stable/12/sys/dev/iicbus/twsi/a10_twsi.c stable/12/sys/dev/iicbus/twsi/twsi.c stable/12/sys/dev/iicbus/twsi/twsi.h Directory Properties: stable/12/ (props changed) Modified: stable/12/sys/dev/iicbus/twsi/a10_twsi.c ============================================================================== --- stable/12/sys/dev/iicbus/twsi/a10_twsi.c Mon Aug 5 17:01:19 2019 (r350595) +++ stable/12/sys/dev/iicbus/twsi/a10_twsi.c Mon Aug 5 17:06:20 2019 (r350596) @@ -1,6 +1,5 @@ /*- - * Copyright (c) 2016 Emmanuel Vadot <manu@freebsd.org> - * All rights reserved. + * Copyright (c) 2016-2019 Emmanuel Vadot <manu@freebsd.org> * * 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,8 +175,8 @@ DEFINE_CLASS_1(iichb, a10_twsi_driver, a10_twsi_method 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); SIMPLEBUS_PNP_INFO(compat_data); Modified: stable/12/sys/dev/iicbus/twsi/twsi.c ============================================================================== --- stable/12/sys/dev/iicbus/twsi/twsi.c Mon Aug 5 17:01:19 2019 (r350595) +++ stable/12/sys/dev/iicbus/twsi/twsi.c Mon Aug 5 17:06:20 2019 (r350596) @@ -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 mas 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, uin { 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, /* 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, 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, 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_ { 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_ 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 ti 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, 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, 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 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,201 @@ out: 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; +} + int twsi_attach(device_t dev) { @@ -430,6 +647,8 @@ twsi_attach(device_t dev) } bus_generic_attach(dev); + config_intrhook_oneshot(twsi_intr_start, dev); + return (0); } @@ -448,6 +667,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); @@ -477,7 +699,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 } }; Modified: stable/12/sys/dev/iicbus/twsi/twsi.h ============================================================================== --- stable/12/sys/dev/iicbus/twsi/twsi.h Mon Aug 5 17:01:19 2019 (r350595) +++ stable/12/sys/dev/iicbus/twsi/twsi.h Mon Aug 5 17:06:20 2019 (r350596) @@ -43,9 +43,19 @@ 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; + void * intrhand; + 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;
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201908051706.x75H6KBO074578>