From patchwork Fri May 24 16:34:11 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Lubomir Popov X-Patchwork-Id: 246204 X-Patchwork-Delegate: hs@denx.de Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from theia.denx.de (theia.denx.de [85.214.87.163]) by ozlabs.org (Postfix) with ESMTP id CFAC92C00A1 for ; Sat, 25 May 2013 02:34:31 +1000 (EST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 11D844A02D; Fri, 24 May 2013 18:34:28 +0200 (CEST) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id TV-DVxhrXRbY; Fri, 24 May 2013 18:34:27 +0200 (CEST) Received: from theia.denx.de (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id C042D4A023; Fri, 24 May 2013 18:34:24 +0200 (CEST) Received: from localhost (localhost [127.0.0.1]) by theia.denx.de (Postfix) with ESMTP id 80F304A023 for ; Fri, 24 May 2013 18:34:21 +0200 (CEST) X-Virus-Scanned: Debian amavisd-new at theia.denx.de Received: from theia.denx.de ([127.0.0.1]) by localhost (theia.denx.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id bYSmQzcm1BUC for ; Fri, 24 May 2013 18:34:15 +0200 (CEST) X-policyd-weight: NOT_IN_SBL_XBL_SPAMHAUS=-1.5 NOT_IN_SPAMCOP=-1.5 BL_NJABL=SKIP(-1.5) (only DNSBL check requested) Received: from extserv.mm-sol.com (ns.mm-sol.com [213.240.235.226]) by theia.denx.de (Postfix) with ESMTPS id 9AC234A021 for ; Fri, 24 May 2013 18:34:13 +0200 (CEST) Received: by extserv.mm-sol.com (Postfix, from userid 33) id CEDC54F881; Fri, 24 May 2013 19:34:11 +0300 (EEST) Received: from 130.204.175.30 (SquirrelMail authenticated user lpopov) by www.mm-sol.com with HTTP; Fri, 24 May 2013 19:34:11 +0300 Message-ID: <7f6ea3fdb5ccfbe97391478e828e468b.squirrel@www.mm-sol.com> Date: Fri, 24 May 2013 19:34:11 +0300 From: "Lubomir Popov" To: u-boot@lists.denx.de User-Agent: SquirrelMail/1.4.23 [SVN] MIME-Version: 1.0 X-Priority: 3 (Normal) Importance: Normal Cc: trini@ti.com Subject: [U-Boot] [PATCH v2] OMAP4/5: I2C: New read, write and probe functions X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.11 Precedence: list Reply-To: lpopov@mm-sol.com List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: u-boot-bounces@lists.denx.de Errors-To: u-boot-bounces@lists.denx.de Tested and built for OMAP4/5 only, but should work on older OMAPs and derivatives as well. - Rewritten i2c_read to operate correctly with all types of chips (old function could not read consistent data from some I2C slaves). - Optimised i2c_write. - New i2c_probe, optionally selectable via CONFIG_I2C_PROBE_WRITE, performs write access vs read. The old probe could hang the system under certain conditions. - The read/write/probe functions try to identify unconfigured bus. - Status functions now read irqstatus_raw as per TRM guidelines. - Driver now supports up to I2C5 (OMAP5). Signed-off-by: Lubomir Popov --- V2 changes: - Probe tries to identify misconfigured pads as well. - Status is retrieved from irqstatus_raw rather than from stat. - Some minor style & format changes. drivers/i2c/omap24xx_i2c.c | 414 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 387 insertions(+), 27 deletions(-) mode change 100644 => 100755 drivers/i2c/omap24xx_i2c.c diff --git a/drivers/i2c/omap24xx_i2c.c b/drivers/i2c/omap24xx_i2c.c old mode 100644 new mode 100755 index 54e9b15..4294701 --- a/drivers/i2c/omap24xx_i2c.c +++ b/drivers/i2c/omap24xx_i2c.c @@ -18,6 +18,19 @@ * * Adapted for OMAP2420 I2C, r-woodruff2@ti.com * + * Copyright (c) 2013 Lubomir Popov , MM Solutions + * New i2c_read, i2c_write and i2c_probe functions, tested and built + * for OMAP4/5 only, but should work on older OMAPs and derivatives + * as well. + * - Rewritten i2c_read to operate correctly with all types of chips + * (old function could not read consistent data from some I2C slaves). + * - Optimized i2c_write. + * - New i2c_probe, optionally selectable via CONFIG_I2C_PROBE_WRITE, + * performs write access vs read. The old probe could hang the system + * under certain conditions. + * - The read/write/probe functions try to identify unconfigured bus. + * - Status functions now read irqstatus_raw as per TRM guidelines. + * - Driver now supports up to I2C5 (OMAP5). */ #include @@ -31,8 +44,15 @@ DECLARE_GLOBAL_DATA_PTR; #define I2C_TIMEOUT 1000 +#if defined(CONFIG_OMAP44XX) || defined(CONFIG_OMAP54XX) +/* Absolutely safe for status update at 100 kHz I2C: */ +#define I2C_WAIT 200 +#else +#define I2C_WAIT 1000 +#endif + static int wait_for_bb(void); -static u16 wait_for_pin(void); +static u16 wait_for_event(void); static void flush_fifo(void); /* @@ -150,6 +170,7 @@ void i2c_init(int speed, int slaveadd) bus_initialized[current_bus] = 1; } +#if !(defined(CONFIG_OMAP44XX) || defined(CONFIG_OMAP54XX)) static int i2c_read_byte(u8 devaddr, u16 regoffset, u8 alen, u8 *value) { int i2c_error = 0; @@ -172,7 +193,7 @@ static int i2c_read_byte(u8 devaddr, u16 regoffset, u8 alen, u8 *value) /* send register offset */ while (1) { - status = wait_for_pin(); + status = wait_for_event(); if (status == 0 || status & I2C_STAT_NACK) { i2c_error = 1; goto read_exit; @@ -204,7 +225,7 @@ static int i2c_read_byte(u8 devaddr, u16 regoffset, u8 alen, u8 *value) /* receive data */ while (1) { - status = wait_for_pin(); + status = wait_for_event(); if (status == 0 || status & I2C_STAT_NACK) { i2c_error = 1; goto read_exit; @@ -231,6 +252,7 @@ read_exit: writew(0, &i2c_base->cnt); return i2c_error; } +#endif static void flush_fifo(void) { u16 stat; @@ -255,6 +277,65 @@ static void flush_fifo(void) } } +#if (defined(CONFIG_OMAP44XX) || defined(CONFIG_OMAP54XX)) && \ + defined(CONFIG_I2C_PROBE_WRITE) +/* + * i2c_probe: Use write access. Allows to identify addresses that are + * write-only (like the config register of dual-port EEPROMs) + */ +int i2c_probe(uchar chip) +{ + u16 status; + int res = 1; /* default = fail */ + + if (chip == readw(&i2c_base->oa)) + return res; + + /* Wait until bus is free */ + if (wait_for_bb()) + return res; + + /* No data transfer, slave addr only */ + writew(0, &i2c_base->cnt); + /* set slave address */ + writew(chip, &i2c_base->sa); + /* stop bit needed here */ + writew(I2C_CON_EN | I2C_CON_MST | I2C_CON_STT | I2C_CON_TRX | + I2C_CON_STP, &i2c_base->con); + + status = wait_for_event(); + + if ((status & ~I2C_STAT_XRDY) == 0 || (status & I2C_STAT_AL)) { + /* + * With current high-level command implementation, notifying + * the user shall flood the console with 127 messages. If + * silent exit is desired upon unconfigured bus, remove the + * following 'if' section: + */ + if (status == I2C_STAT_XRDY) + printf("i2c_probe: pads on bus %d " + "probably not configured (status=0x%x)\n", + current_bus, status); + + goto pr_exit; + } + + /* check for ACK (!NAK) */ + if (!(status & I2C_STAT_NACK)) { + res = 0; /* Device found */ + /* abort transfer (force idle state) */ + writew(I2C_CON_MST | I2C_CON_TRX, &i2c_base->con); /* Reset */ + udelay(1000); + writew(I2C_CON_EN | I2C_CON_MST | I2C_CON_TRX | + I2C_CON_STP, &i2c_base->con); /* STP */ + } +pr_exit: + flush_fifo(); + writew(0xFFFF, &i2c_base->stat); + writew(0, &i2c_base->cnt); + return res; +} +#else int i2c_probe(uchar chip) { u16 status; @@ -275,7 +356,7 @@ int i2c_probe(uchar chip) writew (I2C_CON_EN | I2C_CON_MST | I2C_CON_STT | I2C_CON_STP, &i2c_base->con); while (1) { - status = wait_for_pin(); + status = wait_for_event(); if (status == 0 || status & I2C_STAT_AL) { res = 1; goto probe_exit; @@ -314,7 +395,147 @@ probe_exit: writew(0xFFFF, &i2c_base->stat); return res; } +#endif +#if defined(CONFIG_OMAP44XX) || defined(CONFIG_OMAP54XX) +/* + * i2c_read: Function now uses a single I2C read transaction with bulk transfer + * of the requested number of bytes (note that the 'i2c md' command + * limits this to 16 bytes anyway). If CONFIG_I2C_REPEATED_START is + * defined in the board config header, this transaction shall be with + * Repeated Start (Sr) between the address and data phases; otherwise + * Stop-Start (P-S) shall be used (some I2C chips do require a P-S). + * The address (reg offset) may be 0, 1 or 2 bytes long. + * Function now reads correctly from chips that return more than one + * byte of data per addressed register (like TI temperature sensors), + * or that do not need a register address at all (such as some clock + * distributors). + */ +int i2c_read(uchar chip, uint addr, int alen, uchar *buffer, int len) +{ + int i2c_error = 0; + u16 status; + + if (alen < 0) { + printf("I2C read: addr len < 0\n"); + return 1; + } + if (len < 0) { + printf("I2C read: data len < 0\n"); + return 1; + } + if (buffer == NULL) { + printf("I2C read: NULL pointer passed\n"); + return 1; + } + + if (alen > 2) { + printf("I2C read: addr len %d not supported\n", alen); + return 1; + } + + if (addr + len > (1 << 16)) { + puts("I2C read: address out of range\n"); + return 1; + } + + /* Wait until bus not busy */ + if (wait_for_bb()) + return 1; + + /* Zero, one or two bytes reg address (offset) */ + writew(alen, &i2c_base->cnt); + /* Set slave address */ + writew(chip, &i2c_base->sa); + + if (alen) { + /* Must write reg offset first */ +#ifdef CONFIG_I2C_REPEATED_START + /* No stop bit, use Repeated Start (Sr) */ + writew(I2C_CON_EN | I2C_CON_MST | I2C_CON_STT | + I2C_CON_TRX, &i2c_base->con); +#else + /* Stop - Start (P-S) */ + writew(I2C_CON_EN | I2C_CON_MST | I2C_CON_STT | I2C_CON_STP | + I2C_CON_TRX, &i2c_base->con); +#endif + /* Send register offset */ + while (1) { + status = wait_for_event(); + /* Try to identify bus that is not padconf'd for I2C */ + if (status == I2C_STAT_XRDY) { + i2c_error = 2; + printf("i2c_read (addr phase): pads on bus %d " + "probably not configured (status=0x%x)\n", + current_bus, status); + goto rd_exit; + } + if (status == 0 || status & I2C_STAT_NACK) { + i2c_error = 1; + printf("i2c_read: error waiting for addr ACK " + "(status=0x%x)\n", status); + goto rd_exit; + } + if (alen) { + if (status & I2C_STAT_XRDY) { + alen--; + /* Do we have to use byte access? */ + writeb((addr >> (8 * alen)) & 0xff, + &i2c_base->data); + writew(I2C_STAT_XRDY, &i2c_base->stat); + } + } + if (status & I2C_STAT_ARDY) { + writew(I2C_STAT_ARDY, &i2c_base->stat); + break; + } + } + } + /* Set slave address */ + writew(chip, &i2c_base->sa); + /* Read len bytes from slave */ + writew(len, &i2c_base->cnt); + /* Need stop bit here */ + writew(I2C_CON_EN | I2C_CON_MST | + I2C_CON_STT | I2C_CON_STP, + &i2c_base->con); + + /* Receive data */ + while (1) { + status = wait_for_event(); + /* + * Try to identify bus that is not padconf'd for I2C. This + * state could be left over from previous transactions if + * the address phase is skipped due to alen=0. + */ + if (status == I2C_STAT_XRDY) { + i2c_error = 2; + printf("i2c_read (data phase): pads on bus %d " + "probably not configured (status=0x%x)\n", + current_bus, status); + goto rd_exit; + } + if (status == 0 || status & I2C_STAT_NACK) { + i2c_error = 1; + goto rd_exit; + } + if (status & I2C_STAT_RRDY) { + *buffer++ = readb(&i2c_base->data); + writew(I2C_STAT_RRDY, &i2c_base->stat); + } + if (status & I2C_STAT_ARDY) { + writew(I2C_STAT_ARDY, &i2c_base->stat); + break; + } + } + +rd_exit: + flush_fifo(); + writew(0xFFFF, &i2c_base->stat); + writew(0, &i2c_base->cnt); + return i2c_error; +} +#else int i2c_read(uchar chip, uint addr, int alen, uchar *buffer, int len) { int i; @@ -339,7 +560,111 @@ int i2c_read(uchar chip, uint addr, int alen, uchar *buffer, int len) return 0; } +#endif +#if defined(CONFIG_OMAP44XX) || defined(CONFIG_OMAP54XX) +/* i2c_write: Address (reg offset) may be 0, 1 or 2 bytes long. */ +int i2c_write(uchar chip, uint addr, int alen, uchar *buffer, int len) +{ + int i; + u16 status; + int i2c_error = 0; + + if (alen < 0) { + printf("I2C write: addr len < 0\n"); + return 1; + } + + if (len < 0) { + printf("I2C write: data len < 0\n"); + return 1; + } + + if (buffer == NULL) { + printf("I2C write: NULL pointer passed\n"); + return 1; + } + + if (alen > 2) { + printf("I2C write: addr len %d not supported\n", alen); + return 1; + } + + if (addr + len > (1 << 16)) { + printf("I2C write: address 0x%x + 0x%x out of range\n", + addr, len); + return 1; + } + + /* Wait until bus not busy */ + if (wait_for_bb()) + return 1; + + /* Start address phase - will write regoffset + len bytes data */ + writew(alen + len, &i2c_base->cnt); + /* Set slave address */ + writew(chip, &i2c_base->sa); + /* Stop bit needed here */ + writew(I2C_CON_EN | I2C_CON_MST | I2C_CON_STT | I2C_CON_TRX | + I2C_CON_STP, &i2c_base->con); + + while (alen) { + /* Must write reg offset (one or two bytes) */ + status = wait_for_event(); + /* Try to identify bus that is not padconf'd for I2C */ + if (status == I2C_STAT_XRDY) { + i2c_error = 2; + printf("i2c_write: pads on bus %d " + "probably not configured (status=0x%x)\n", + current_bus, status); + goto wr_exit; + } + if (status == 0 || status & I2C_STAT_NACK) { + i2c_error = 1; + printf("i2c_write: error waiting for addr ACK " + "(status=0x%x)\n", status); + goto wr_exit; + } + if (status & I2C_STAT_XRDY) { + alen--; + writeb((addr >> (8 * alen)) & 0xff, &i2c_base->data); + writew(I2C_STAT_XRDY, &i2c_base->stat); + } + else { + i2c_error = 1; + printf("i2c_write: bus not ready for addr Tx " + "(status=0x%x)\n", status); + goto wr_exit; + } + } + /* Address phase is over, now write data */ + for (i = 0; i < len; i++) { + status = wait_for_event(); + if (status == 0 || status & I2C_STAT_NACK) { + i2c_error = 1; + printf("i2c_write: error waiting for data ACK " + "(status=0x%x)\n", status); + goto wr_exit; + } + if (status & I2C_STAT_XRDY) { + writeb(buffer[i], &i2c_base->data); + writew(I2C_STAT_XRDY, &i2c_base->stat); + } + else { + i2c_error = 1; + printf("i2c_write: bus not ready for data Tx " + "(i=%d)\n", i); + goto wr_exit; + } + } + +wr_exit: + flush_fifo(); + writew(0xFFFF, &i2c_base->stat); + writew(0, &i2c_base->cnt); + return i2c_error; +} +#else int i2c_write(uchar chip, uint addr, int alen, uchar *buffer, int len) { int i; @@ -374,7 +699,7 @@ int i2c_write(uchar chip, uint addr, int alen, uchar *buffer, int len) /* Send address and data */ for (i = -alen; i < len; i++) { - status = wait_for_pin(); + status = wait_for_event(); if (status == 0 || status & I2C_STAT_NACK) { i2c_error = 1; @@ -404,43 +729,70 @@ write_exit: writew(0xFFFF, &i2c_base->stat); return i2c_error; } +#endif +/* + * Wait for the bus to be free by checking the Bus Busy (BB) + * bit to become clear + */ static int wait_for_bb(void) { int timeout = I2C_TIMEOUT; u16 stat; writew(0xFFFF, &i2c_base->stat); /* clear current interrupts...*/ +#if defined(CONFIG_OMAP44XX) || defined(CONFIG_OMAP54XX) + /* Read RAW status */ + while ((stat = readw(&i2c_base->irqstatus_raw) & + I2C_STAT_BB) && timeout--) { +#else while ((stat = readw(&i2c_base->stat) & I2C_STAT_BB) && timeout--) { +#endif writew(stat, &i2c_base->stat); - udelay(1000); + udelay(I2C_WAIT); } if (timeout <= 0) { - printf("timed out in wait_for_bb: I2C_STAT=%x\n", - readw(&i2c_base->stat)); + printf("Timed out in wait_for_bb: irqstatus_raw=%04x\n", + readw(&i2c_base->irqstatus_raw)); return 1; } writew(0xFFFF, &i2c_base->stat); /* clear delayed stuff*/ return 0; } -static u16 wait_for_pin(void) +/* + * Wait for the I2C controller to complete current action + * and update status + */ +static u16 wait_for_event(void) { u16 status; int timeout = I2C_TIMEOUT; do { - udelay(1000); + udelay(I2C_WAIT); +#if defined(CONFIG_OMAP44XX) || defined(CONFIG_OMAP54XX) + /* Read RAW status */ + status = readw(&i2c_base->irqstatus_raw); +#else status = readw(&i2c_base->stat); +#endif } while (!(status & (I2C_STAT_ROVR | I2C_STAT_XUDF | I2C_STAT_XRDY | I2C_STAT_RRDY | I2C_STAT_ARDY | I2C_STAT_NACK | I2C_STAT_AL)) && timeout--); if (timeout <= 0) { - printf("timed out in wait_for_pin: I2C_STAT=%x\n", - readw(&i2c_base->stat)); + printf("Timed out in wait_for_event: irqstatus_raw=%04x\n", + readw(&i2c_base->irqstatus_raw)); + /* + * If irqstatus_raw is still 0 here, probably the bus pads have + * not been configured for I2C, and/or pull-ups are missing. + */ + printf("Check if pads/pull-ups of bus %d " + "are properly configured\n", + current_bus); writew(0xFFFF, &i2c_base->stat); status = 0; } @@ -455,23 +807,31 @@ int i2c_set_bus_num(unsigned int bus) return -1; } -#if I2C_BUS_MAX == 4 - if (bus == 3) - i2c_base = (struct i2c *)I2C_BASE4; - else - if (bus == 2) - i2c_base = (struct i2c *)I2C_BASE3; - else + switch (bus) { + default: + bus = 0; /* Fall through */ + case 0: + i2c_base = (struct i2c *)I2C_BASE1; + break; + case 1: + i2c_base = (struct i2c *)I2C_BASE2; + break; +#if (I2C_BUS_MAX > 2) + case 2: + i2c_base = (struct i2c *)I2C_BASE3; + break; +#if (I2C_BUS_MAX > 3) + case 3: + i2c_base = (struct i2c *)I2C_BASE4; + break; +#if (I2C_BUS_MAX > 4) && defined(I2C_BASE5) + case 4: + i2c_base = (struct i2c *)I2C_BASE5; + break; #endif -#if I2C_BUS_MAX == 3 - if (bus == 2) - i2c_base = (struct i2c *)I2C_BASE3; - else #endif - if (bus == 1) - i2c_base = (struct i2c *)I2C_BASE2; - else - i2c_base = (struct i2c *)I2C_BASE1; +#endif + } current_bus = bus;