diff options
author | Alexandru Gagniuc <mr.nuke.me@gmail.com> | 2013-12-28 21:50:54 -0500 |
---|---|---|
committer | Alexandru Gagniuc <mr.nuke.me@gmail.com> | 2014-01-08 23:03:46 +0100 |
commit | 5c4bde70ae92626fc92ee51249912961a668bba1 (patch) | |
tree | 12cd5e745bb0d345e28db51664de8e9e4ca1bb6c | |
parent | bc30b2b225a31f7fcf4e25014a73739641a8df71 (diff) |
cpu/allwinner/a10: Add basic TWI (I²C) driver
Change-Id: I11b10301199e5ff1a45d9b7d2958cc7b6667a29c
Signed-off-by: Alexandru Gagniuc <mr.nuke.me@gmail.com>
Reviewed-on: http://review.coreboot.org/4588
Tested-by: build bot (Jenkins)
Reviewed-by: Paul Menzel <paulepanter@users.sourceforge.net>
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
-rw-r--r-- | src/cpu/allwinner/a10/Makefile.inc | 1 | ||||
-rw-r--r-- | src/cpu/allwinner/a10/twi.c | 206 | ||||
-rw-r--r-- | src/cpu/allwinner/a10/twi.h | 58 |
3 files changed, 265 insertions, 0 deletions
diff --git a/src/cpu/allwinner/a10/Makefile.inc b/src/cpu/allwinner/a10/Makefile.inc index fecaf0e543..686552921a 100644 --- a/src/cpu/allwinner/a10/Makefile.inc +++ b/src/cpu/allwinner/a10/Makefile.inc @@ -11,6 +11,7 @@ romstage-y += bootblock_media.c ramstage-y += uart.c ramstage-y += uart_console.c ramstage-y += timer.c +ramstage-y += twi.c ramstage-y += monotonic_timer.c ramstage-y += bootblock_media.c diff --git a/src/cpu/allwinner/a10/twi.c b/src/cpu/allwinner/a10/twi.c new file mode 100644 index 0000000000..692a498a36 --- /dev/null +++ b/src/cpu/allwinner/a10/twi.c @@ -0,0 +1,206 @@ +/* + * Setup helpers for Two Wire Interface (TWI) (I²C) Allwinner CPUs + * + * Only functionality for I²C master is provided. + * Largely based on the uboot-sunxi code. + * + * Copyright (C) 2012 Henrik Nordstrom <henrik@henriknordstrom.net> + * Copyright (C) 2013 Alexandru Gagniuc <mr.nuke.me@gmail.com> + * Subject to the GNU GPL v2, or (at your option) any later version. + */ + +#include "memmap.h" +#include "twi.h" + +#include <arch/io.h> +#include <delay.h> +#include <device/i2c.h> + +#define TWI_BASE(n) (A1X_TWI0_BASE + 0x400 * (n)) + +#define TWI_TIMEOUT (50 * 1000) + +static u8 is_busy(struct a1x_twi *twi) +{ + return (read32(&twi->stat) != TWI_STAT_IDLE); +} + +static enum cb_err wait_until_idle(struct a1x_twi *twi) +{ + u32 i = TWI_TIMEOUT; + while (i-- && is_busy((twi))) + udelay(1); + return i ? CB_SUCCESS : CB_ERR; +} + +/* FIXME: This function is basic, and unintelligent */ +static void configure_clock(struct a1x_twi *twi, u32 speed_hz) +{ + /* FIXME: We assume clock is 24MHz, which may not be the case */ + u32 apb_clk = 24000000, m, n; + + /* Pre-divide the clock by 8 */ + n = 3; + m = (apb_clk >> n) / speed_hz; + write32(TWI_CLK_M(m) | TWI_CLK_N(n), &twi->clk); +} + +void a1x_twi_init(u8 bus, u32 speed_hz) +{ + u32 i = TWI_TIMEOUT; + struct a1x_twi *twi = (void *)TWI_BASE(bus); + + configure_clock(twi, speed_hz); + + /* Enable the I²C bus */ + write32(TWI_CTL_BUS_EN, &twi->ctl); + /* Issue soft reset */ + write32(1, &twi->reset); + + while (i-- && read32(&twi->reset)) + udelay(1); +} + +static void clear_interrupt_flag(struct a1x_twi *twi) +{ + write32(read32(&twi->ctl) & ~TWI_CTL_INT_FLAG, &twi->ctl); +} + +static void i2c_send_data(struct a1x_twi *twi, u8 data) +{ + write32(data, &twi->data); + clear_interrupt_flag(twi); +} + +static enum twi_status wait_for_status(struct a1x_twi *twi) +{ + u32 i = TWI_TIMEOUT; + /* Wait until interrupt is asserted again */ + while (i-- && !(read32(&twi->ctl) & TWI_CTL_INT_FLAG)) + udelay(1); + /* A timeout here most likely indicates a bus error */ + return i ? read32(&twi->stat) : TWI_STAT_BUS_ERROR; +} + +static void i2c_send_start(struct a1x_twi *twi) +{ + u32 reg32, i; + + /* Send START condition */ + reg32 = read32(&twi->ctl); + reg32 &= ~TWI_CTL_INT_FLAG; + reg32 |= TWI_CTL_M_START; + write32(reg32, &twi->ctl); + + /* M_START is automatically cleared after condition is transmitted */ + i = TWI_TIMEOUT; + while (i-- && (read32(&twi->ctl) & TWI_CTL_M_START)) + udelay(1); +} + +static void i2c_send_stop(struct a1x_twi *twi) +{ + u32 reg32; + + /* Send STOP condition */ + reg32 = read32(&twi->ctl); + reg32 &= ~TWI_CTL_INT_FLAG; + reg32 |= TWI_CTL_M_STOP; + write32(reg32, &twi->ctl); +} + +int i2c_read(unsigned bus, unsigned chip, unsigned addr, + unsigned alen, uint8_t *buf, unsigned len) +{ + unsigned count = len; + enum twi_status expected_status; + struct a1x_twi *twi = (void *)TWI_BASE(bus); + + if (wait_until_idle(twi) != CB_SUCCESS) + return CB_ERR; + + i2c_send_start(twi); + if (wait_for_status(twi) != TWI_STAT_TX_START) + return CB_ERR; + + /* Send chip address */ + i2c_send_data(twi, chip << 1); + if (wait_for_status(twi) != TWI_STAT_TX_AW_ACK) + return CB_ERR; + + /* Send data address */ + i2c_send_data(twi, addr); + if (wait_for_status(twi) != TWI_STAT_TXD_ACK) + return CB_ERR; + + /* Send restart for read */ + i2c_send_start(twi); + if (wait_for_status(twi) != TWI_STAT_TX_RSTART) + return CB_ERR; + + /* Send chip address */ + i2c_send_data(twi, chip << 1 | 1); + if (wait_for_status(twi) != TWI_STAT_TX_AR_ACK) + return CB_ERR; + + /* Start ACK-ing received data */ + setbits_le32(&twi->ctl, TWI_CTL_A_ACK); + expected_status = TWI_STAT_RXD_ACK; + + /* Read data */ + while (count > 0) { + if (count == 1) { + /* Do not ACK the last byte */ + clrbits_le32(&twi->ctl, TWI_CTL_A_ACK); + expected_status = TWI_STAT_RXD_NAK; + } + + clear_interrupt_flag(twi); + + if (wait_for_status(twi) != expected_status) + return CB_ERR; + + *buf++ = read32(&twi->data); + count--; + } + + i2c_send_stop(twi); + + return len; +} + +int i2c_write(unsigned bus, unsigned chip, unsigned addr, + unsigned alen, const uint8_t *buf, unsigned len) +{ + unsigned count = len; + struct a1x_twi *twi = (void *)TWI_BASE(bus); + + if (wait_until_idle(twi) != CB_SUCCESS) + return CB_ERR; + + i2c_send_start(twi); + if (wait_for_status(twi) != TWI_STAT_TX_START) + return CB_ERR; + + /* Send chip address */ + i2c_send_data(twi, chip << 1); + if (wait_for_status(twi) != TWI_STAT_TX_AW_ACK) + return CB_ERR; + + /* Send data address */ + i2c_send_data(twi, addr); + if (wait_for_status(twi) != TWI_STAT_TXD_ACK) + return CB_ERR; + + /* Send data */ + while (count > 0) { + i2c_send_data(twi, *buf++); + if (wait_for_status(twi) != TWI_STAT_TXD_ACK) + return CB_ERR; + count--; + } + + i2c_send_stop(twi); + + return len; +} diff --git a/src/cpu/allwinner/a10/twi.h b/src/cpu/allwinner/a10/twi.h new file mode 100644 index 0000000000..c5f2974140 --- /dev/null +++ b/src/cpu/allwinner/a10/twi.h @@ -0,0 +1,58 @@ +/* + * Definitions Two Wire Interface (TWI) (I²C) Allwinner CPUs + * + * Copyright (C) 2013 Alexandru Gagniuc <mr.nuke.me@gmail.com> + * Subject to the GNU GPL v2, or (at your option) any later version. + */ + +#ifndef CPU_ALLWINNER_A10_TWI_H +#define CPU_ALLWINNER_A10_TWI_H + +#include <types.h> + +/* TWI_CTL values */ +#define TWI_CTL_INT_EN (1 << 7) +#define TWI_CTL_BUS_EN (1 << 6) +#define TWI_CTL_M_START (1 << 5) +#define TWI_CTL_M_STOP (1 << 4) +#define TWI_CTL_INT_FLAG (1 << 3) +#define TWI_CTL_A_ACK (1 << 2) + +/* TWI_STAT values */ +enum twi_status { + TWI_STAT_BUS_ERROR = 0x00, /**< Bus error */ + TWI_STAT_TX_START = 0x08, /**< START sent */ + TWI_STAT_TX_RSTART = 0x10, /**< Repeated START sent */ + TWI_STAT_TX_AW_ACK = 0x18, /**< Sent address+read, ACK */ + TWI_STAT_TX_AW_NAK = 0x20, /**< Sent address+read, NAK */ + TWI_STAT_TXD_ACK = 0x28, /**< Sent data, got ACK */ + TWI_STAT_TXD_NAK = 0x30, /**< Sent data, no ACK */ + TWI_STAT_LOST_ARB = 0x38, /**< Lost arbitration */ + TWI_STAT_TX_AR_ACK = 0x40, /**< Sent address+write, ACK */ + TWI_STAT_TX_AR_NAK = 0x48, /**< Sent address+write, NAK */ + TWI_STAT_RXD_ACK = 0x50, /**< Got data, sent ACK */ + TWI_STAT_RXD_NAK = 0x58, /**< Got data, no ACK */ + TWI_STAT_IDLE = 0xf8, /**< Bus idle*/ +}; + +/* TWI_CLK values */ +#define TWI_CLK_M_MASK (0xf << 3) +#define TWI_CLK_M(m) (((m - 1) << 3) & TWI_CLK_M_MASK) +#define TWI_CLK_N_MASK (0x7 << 0) +#define TWI_CLK_N(n) (((n) << 3) & TWI_CLK_N_MASK) + +struct a1x_twi { + u32 addr; /**< 0x00: Slave address */ + u32 xaddr; /**< 0x04: Extended slave address */ + u32 data; /**< 0x08: Data byte */ + u32 ctl; /**< 0x0C: Control register */ + u32 stat; /**< 0x10: Status register */ + u32 clk; /**< 0x14: Clock control register */ + u32 reset; /**< 0x18: Software reset */ + u32 efr; /**< 0x1C: Enhanced Feature register */ + u32 lcr; /**< 0x20: Line control register */ +}; + +void a1x_twi_init(u8 bus, u32 speed_hz); + +#endif /* CPU_ALLWINNER_A10_TWI_H */ |