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>
This commit is contained in:
@@ -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
|
||||
|
||||
|
206
src/cpu/allwinner/a10/twi.c
Normal file
206
src/cpu/allwinner/a10/twi.c
Normal file
@@ -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;
|
||||
}
|
58
src/cpu/allwinner/a10/twi.h
Normal file
58
src/cpu/allwinner/a10/twi.h
Normal file
@@ -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 */
|
Reference in New Issue
Block a user